├── hasura ├── metadata │ ├── allow_list.yaml │ ├── functions.yaml │ ├── actions.graphql │ ├── cron_triggers.yaml │ ├── remote_schemas.yaml │ ├── version.yaml │ ├── query_collections.yaml │ ├── actions.yaml │ └── tables.yaml ├── config-example.yaml └── migrations │ └── 100_init_example_app │ └── up.sql ├── src ├── react-app-env.d.ts ├── styles │ └── tailwind.css ├── components │ ├── svg │ │ ├── index.tsx │ │ └── loading.tsx │ ├── layout │ │ ├── index.tsx │ │ ├── main.tsx │ │ └── layout.tsx │ ├── ui │ │ ├── index.tsx │ │ ├── avatar.tsx │ │ ├── button.tsx │ │ └── text-field.tsx │ ├── app │ │ ├── dashboard.tsx │ │ ├── main-container.tsx │ │ ├── header-user.tsx │ │ ├── todos-form.tsx │ │ ├── header.tsx │ │ ├── settings.tsx │ │ ├── todos.tsx │ │ └── files.tsx │ ├── auth-gate.tsx │ ├── new-email.tsx │ ├── routers │ │ ├── router-app.tsx │ │ └── router.tsx │ ├── password-set.tsx │ ├── password-forgot.tsx │ ├── login.tsx │ └── register.tsx ├── images │ ├── github.png │ ├── google.png │ └── facebook.png ├── utils │ ├── config-example.ts │ └── nhost.ts ├── gql │ ├── users.ts │ ├── files.ts │ └── todos.ts ├── global.d.ts ├── generated │ ├── getTodos.ts │ ├── getFiles.ts │ ├── s_getTodos.ts │ ├── s_getFiles.ts │ ├── insertFile.ts │ ├── insertTodo.ts │ ├── updateTodo.ts │ ├── deleteFiles.ts │ ├── deleteTodos.ts │ ├── s_userGetSelf.ts │ └── globalTypes.ts ├── index.tsx └── serviceWorker.ts ├── public ├── robots.txt ├── favicon.ico ├── manifest.json └── index.html ├── README.md ├── postcss.config.js ├── tailwind.config.js ├── apollo.config.js ├── .gitignore ├── tsconfig.json └── package.json /hasura/metadata/allow_list.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /hasura/metadata/functions.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /hasura/metadata/actions.graphql: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /hasura/metadata/cron_triggers.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /hasura/metadata/remote_schemas.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /hasura/metadata/version.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | -------------------------------------------------------------------------------- /hasura/metadata/query_collections.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/components/svg/index.tsx: -------------------------------------------------------------------------------- 1 | import { SvgLoading } from "./loading"; 2 | 3 | export { SvgLoading }; 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhost/nhost-react-typescript-example-app/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | All example apps are moved to: [https://github.com/nhost-examples](https://github.com/nhost-examples). 2 | -------------------------------------------------------------------------------- /src/images/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhost/nhost-react-typescript-example-app/HEAD/src/images/github.png -------------------------------------------------------------------------------- /src/images/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhost/nhost-react-typescript-example-app/HEAD/src/images/google.png -------------------------------------------------------------------------------- /src/images/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nhost/nhost-react-typescript-example-app/HEAD/src/images/facebook.png -------------------------------------------------------------------------------- /hasura/metadata/actions.yaml: -------------------------------------------------------------------------------- 1 | actions: [] 2 | custom_types: 3 | enums: [] 4 | input_objects: [] 5 | objects: [] 6 | scalars: [] 7 | -------------------------------------------------------------------------------- /src/components/layout/index.tsx: -------------------------------------------------------------------------------- 1 | import { Layout } from "./layout"; 2 | import { Main } from "./main"; 3 | 4 | export { Layout, Main }; 5 | -------------------------------------------------------------------------------- /src/utils/config-example.ts: -------------------------------------------------------------------------------- 1 | export const BACKEND_ENDPOINT = "https://backend-[id].nhost.app"; 2 | export const GRAPHQL_ENDPOINT = "https://hasura-[id].nhost.app/v1/graphql"; 3 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const tailwindcss = require("tailwindcss"); 2 | 3 | module.exports = { 4 | plugins: [tailwindcss("./tailwind.config.js"), require("autoprefixer")], 5 | }; 6 | -------------------------------------------------------------------------------- /src/components/ui/index.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar } from "./avatar"; 2 | import { Button } from "./button"; 3 | import { TextField } from "./text-field"; 4 | 5 | export { Avatar, Button, TextField }; 6 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [], 3 | theme: { 4 | extend: {}, 5 | }, 6 | variants: { 7 | backgroundColor: ["responsive", "odd", "hover", "focus"], 8 | }, 9 | plugins: [], 10 | }; 11 | -------------------------------------------------------------------------------- /hasura/config-example.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | endpoint: https://hasura-[id].nhost.app 3 | admin_secret: 4 | metadata_directory: metadata 5 | actions: 6 | kind: synchronous 7 | handler_webhook_baseurl: http://localhost:3000 8 | -------------------------------------------------------------------------------- /src/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type AvatarProps = { 4 | children: JSX.Element | string; 5 | }; 6 | 7 | export function Avatar({ children }: AvatarProps) { 8 | return
Avatar.. {children}
; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/app/dashboard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export function Dashboard() { 4 | return ( 5 |
6 | Welcome to Nhost's example app in React, Typescript and Tailwind! 7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /apollo.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | client: { 3 | service: { 4 | name: "GraphQL", 5 | url: "https://hasura-[id].nhost.app/v1/graphql", 6 | headers: { 7 | "x-hasura-admin-secret": "my-admin-secret-from-nhost", 8 | }, 9 | }, 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /src/gql/users.ts: -------------------------------------------------------------------------------- 1 | import gql from "graphql-tag"; 2 | 3 | export const S_USER_GET_SELF = gql` 4 | subscription s_userGetSelf($id: uuid!) { 5 | users_by_pk(id: $id) { 6 | id 7 | display_name 8 | avatar_url 9 | account { 10 | email 11 | } 12 | } 13 | } 14 | `; 15 | -------------------------------------------------------------------------------- /src/utils/nhost.ts: -------------------------------------------------------------------------------- 1 | import nhost from "nhost-js-sdk"; 2 | import { BACKEND_ENDPOINT } from "utils/config"; 3 | 4 | const config = { 5 | base_url: BACKEND_ENDPOINT, 6 | }; 7 | 8 | nhost.initializeApp(config); 9 | 10 | const auth = nhost.auth(); 11 | const storage = nhost.storage(); 12 | 13 | export { auth, storage }; 14 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Nhost React", 3 | "name": "Nhost React Example App", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/components/layout/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classNames from "classnames"; 3 | 4 | type LayoutProps = { 5 | children: React.ReactNode; 6 | className?: string; 7 | [name: string]: any; 8 | }; 9 | 10 | export function Main(props: LayoutProps) { 11 | const { children, className } = props; 12 | 13 | const classes = classNames(["container mx-auto", className]); 14 | 15 | return
{children}
; 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | 26 | src/utils/config.ts 27 | hasura/config.yaml -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | type gql_uuid = string; 2 | type gql_timestamptz = string; 3 | type gql_jsonb = any; 4 | type gql_citext = string; 5 | 6 | interface useAuthProps { 7 | signedIn: boolean | null; 8 | } 9 | 10 | declare module "react-nhost" { 11 | export function NhostAuthProvider(auth: any): JSX.Element; 12 | export function NhostApolloProvider( 13 | auth: any, 14 | gql_endpoint: string 15 | ): JSX.Element; 16 | export function useAuth(): useAuthProps; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/app/main-container.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Header } from "components/app/header"; 3 | 4 | export interface IMainContainerProps { 5 | children: React.ReactNode; 6 | } 7 | 8 | export function MainContainer(props: IMainContainerProps) { 9 | const { children } = props; 10 | return ( 11 |
12 |
13 |
14 |
{children}
15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /src/components/auth-gate.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Redirect } from "react-router-dom"; 3 | import { useAuth } from "react-nhost"; 4 | 5 | type AuthGateProps = { 6 | children: JSX.Element; 7 | }; 8 | 9 | export function AuthGate({ children }: AuthGateProps): JSX.Element { 10 | const { signedIn } = useAuth(); 11 | 12 | if (signedIn === null) { 13 | return
Loading...
; 14 | } 15 | 16 | if (!signedIn) { 17 | return ; 18 | } 19 | 20 | // user is logged in 21 | return children; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/layout/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classNames from "classnames"; 3 | import { Header } from "components/app/header"; 4 | 5 | type LayoutProps = { 6 | children: React.ReactNode; 7 | className?: string; 8 | [name: string]: any; 9 | }; 10 | 11 | export function Layout(props: LayoutProps) { 12 | const { children, className } = props; 13 | 14 | const classes = classNames([className]); 15 | 16 | return ( 17 |
18 |
19 | {children} 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "baseUrl": "src", 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react" 18 | }, 19 | "include": ["src"] 20 | } 21 | -------------------------------------------------------------------------------- /src/components/new-email.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useParams } from "react-router-dom"; 3 | import { auth } from "utils/nhost"; 4 | 5 | export function NewEmail() { 6 | const [progress, setProgress] = useState("LOADING"); 7 | const { ticket } = useParams(); 8 | 9 | useEffect(() => { 10 | const changeEmailHandler = async () => { 11 | try { 12 | await auth.changeEmailChange(ticket); 13 | } catch (error) { 14 | return setProgress("FAILED"); 15 | } 16 | return setProgress("COMPLETED"); 17 | }; 18 | changeEmailHandler(); 19 | }, [ticket]); 20 | 21 | return
{progress}
; 22 | } 23 | -------------------------------------------------------------------------------- /src/generated/getTodos.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // @generated 4 | // This file was automatically generated and should not be edited. 5 | 6 | // ==================================================== 7 | // GraphQL query operation: getTodos 8 | // ==================================================== 9 | 10 | export interface getTodos_todos { 11 | __typename: "todos"; 12 | id: gql_uuid; 13 | todo: string; 14 | done: boolean; 15 | updated_at: gql_timestamptz; 16 | } 17 | 18 | export interface getTodos { 19 | /** 20 | * fetch data from the table: "todos" 21 | */ 22 | todos: getTodos_todos[]; 23 | } 24 | 25 | export interface getTodosVariables { 26 | limit: number; 27 | } 28 | -------------------------------------------------------------------------------- /src/generated/getFiles.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // @generated 4 | // This file was automatically generated and should not be edited. 5 | 6 | // ==================================================== 7 | // GraphQL query operation: getFiles 8 | // ==================================================== 9 | 10 | export interface getFiles_files { 11 | __typename: "files"; 12 | id: gql_uuid; 13 | created_at: gql_timestamptz; 14 | file_path: string; 15 | downloadable_url: string; 16 | } 17 | 18 | export interface getFiles { 19 | /** 20 | * fetch data from the table: "files" 21 | */ 22 | files: getFiles_files[]; 23 | } 24 | 25 | export interface getFilesVariables { 26 | limit: number; 27 | } 28 | -------------------------------------------------------------------------------- /src/generated/s_getTodos.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // @generated 4 | // This file was automatically generated and should not be edited. 5 | 6 | // ==================================================== 7 | // GraphQL subscription operation: s_getTodos 8 | // ==================================================== 9 | 10 | export interface s_getTodos_todos { 11 | __typename: "todos"; 12 | id: gql_uuid; 13 | todo: string; 14 | done: boolean; 15 | updated_at: gql_timestamptz; 16 | } 17 | 18 | export interface s_getTodos { 19 | /** 20 | * fetch data from the table: "todos" 21 | */ 22 | todos: s_getTodos_todos[]; 23 | } 24 | 25 | export interface s_getTodosVariables { 26 | limit: number; 27 | } 28 | -------------------------------------------------------------------------------- /src/generated/s_getFiles.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // @generated 4 | // This file was automatically generated and should not be edited. 5 | 6 | // ==================================================== 7 | // GraphQL subscription operation: s_getFiles 8 | // ==================================================== 9 | 10 | export interface s_getFiles_files { 11 | __typename: "files"; 12 | id: gql_uuid; 13 | created_at: gql_timestamptz; 14 | file_path: string; 15 | downloadable_url: string; 16 | } 17 | 18 | export interface s_getFiles { 19 | /** 20 | * fetch data from the table: "files" 21 | */ 22 | files: s_getFiles_files[]; 23 | } 24 | 25 | export interface s_getFilesVariables { 26 | limit: number; 27 | } 28 | -------------------------------------------------------------------------------- /src/generated/insertFile.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // @generated 4 | // This file was automatically generated and should not be edited. 5 | 6 | import { files_insert_input } from "./globalTypes"; 7 | 8 | // ==================================================== 9 | // GraphQL mutation operation: insertFile 10 | // ==================================================== 11 | 12 | export interface insertFile_insert_files_one { 13 | __typename: "files"; 14 | id: gql_uuid; 15 | } 16 | 17 | export interface insertFile { 18 | /** 19 | * insert a single row into the table: "files" 20 | */ 21 | insert_files_one: insertFile_insert_files_one | null; 22 | } 23 | 24 | export interface insertFileVariables { 25 | file: files_insert_input; 26 | } 27 | -------------------------------------------------------------------------------- /src/generated/insertTodo.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // @generated 4 | // This file was automatically generated and should not be edited. 5 | 6 | import { todos_insert_input } from "./globalTypes"; 7 | 8 | // ==================================================== 9 | // GraphQL mutation operation: insertTodo 10 | // ==================================================== 11 | 12 | export interface insertTodo_insert_todos_one { 13 | __typename: "todos"; 14 | id: gql_uuid; 15 | } 16 | 17 | export interface insertTodo { 18 | /** 19 | * insert a single row into the table: "todos" 20 | */ 21 | insert_todos_one: insertTodo_insert_todos_one | null; 22 | } 23 | 24 | export interface insertTodoVariables { 25 | todo: todos_insert_input; 26 | } 27 | -------------------------------------------------------------------------------- /src/generated/updateTodo.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // @generated 4 | // This file was automatically generated and should not be edited. 5 | 6 | import { todos_set_input } from "./globalTypes"; 7 | 8 | // ==================================================== 9 | // GraphQL mutation operation: updateTodo 10 | // ==================================================== 11 | 12 | export interface updateTodo_update_todos_by_pk { 13 | __typename: "todos"; 14 | id: gql_uuid; 15 | todo: string; 16 | done: boolean; 17 | } 18 | 19 | export interface updateTodo { 20 | /** 21 | * update single row of the table: "todos" 22 | */ 23 | update_todos_by_pk: updateTodo_update_todos_by_pk | null; 24 | } 25 | 26 | export interface updateTodoVariables { 27 | id: gql_uuid; 28 | todo: todos_set_input; 29 | } 30 | -------------------------------------------------------------------------------- /src/generated/deleteFiles.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // @generated 4 | // This file was automatically generated and should not be edited. 5 | 6 | import { files_bool_exp } from "./globalTypes"; 7 | 8 | // ==================================================== 9 | // GraphQL mutation operation: deleteFiles 10 | // ==================================================== 11 | 12 | export interface deleteFiles_delete_files { 13 | __typename: "files_mutation_response"; 14 | /** 15 | * number of affected rows by the mutation 16 | */ 17 | affected_rows: number; 18 | } 19 | 20 | export interface deleteFiles { 21 | /** 22 | * delete data from the table: "files" 23 | */ 24 | delete_files: deleteFiles_delete_files | null; 25 | } 26 | 27 | export interface deleteFilesVariables { 28 | where: files_bool_exp; 29 | } 30 | -------------------------------------------------------------------------------- /src/generated/deleteTodos.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // @generated 4 | // This file was automatically generated and should not be edited. 5 | 6 | import { todos_bool_exp } from "./globalTypes"; 7 | 8 | // ==================================================== 9 | // GraphQL mutation operation: deleteTodos 10 | // ==================================================== 11 | 12 | export interface deleteTodos_delete_todos { 13 | __typename: "todos_mutation_response"; 14 | /** 15 | * number of affected rows by the mutation 16 | */ 17 | affected_rows: number; 18 | } 19 | 20 | export interface deleteTodos { 21 | /** 22 | * delete data from the table: "todos" 23 | */ 24 | delete_todos: deleteTodos_delete_todos | null; 25 | } 26 | 27 | export interface deleteTodosVariables { 28 | where: todos_bool_exp; 29 | } 30 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classNames from "classnames"; 3 | 4 | type ButtonProps = { 5 | children: React.ReactNode; 6 | disabled?: boolean; 7 | fullWidth?: boolean; 8 | [name: string]: any; 9 | }; 10 | 11 | export function Button(props: ButtonProps) { 12 | const { children, className, disabled, fullWidth, ...rest } = props; 13 | 14 | const classes = classNames([ 15 | "px-4 py-2 rounded-md bg-indigo-600 text-white uppercase", 16 | { 17 | "w-full": fullWidth, 18 | "opacity-50": disabled, 19 | "cursor-not-allowed": disabled, 20 | }, 21 | className, 22 | ]); 23 | return ( 24 | 27 | ); 28 | } 29 | 30 | Button.defaultProps = { 31 | className: "", 32 | disabled: false, 33 | fullWidth: false, 34 | }; 35 | -------------------------------------------------------------------------------- /src/gql/files.ts: -------------------------------------------------------------------------------- 1 | import gql from "graphql-tag"; 2 | 3 | export const GET_FILES = gql` 4 | query getFiles($limit: Int!) { 5 | files(limit: $limit) { 6 | id 7 | created_at 8 | file_path 9 | downloadable_url 10 | } 11 | } 12 | `; 13 | 14 | export const S_GET_FILES = gql` 15 | subscription s_getFiles($limit: Int!) { 16 | files(limit: $limit, order_by: { created_at: desc }) { 17 | id 18 | created_at 19 | file_path 20 | downloadable_url 21 | } 22 | } 23 | `; 24 | 25 | export const INSERT_FILE = gql` 26 | mutation insertFile($file: files_insert_input!) { 27 | insert_files_one(object: $file) { 28 | id 29 | } 30 | } 31 | `; 32 | 33 | export const DELETE_FILES = gql` 34 | mutation deleteFiles($where: files_bool_exp!) { 35 | delete_files(where: $where) { 36 | affected_rows 37 | } 38 | } 39 | `; 40 | -------------------------------------------------------------------------------- /src/components/ui/text-field.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classNames from "classnames"; 3 | 4 | type TextFieldProps = { 5 | type: string; 6 | disabled?: boolean; 7 | className?: string; 8 | fullWidth?: boolean; 9 | [name: string]: any; 10 | }; 11 | 12 | export function TextField(props: TextFieldProps) { 13 | const { type, disabled, className, fullWidth, ...rest } = props; 14 | 15 | const classes = classNames([ 16 | "py-2 px-4 border border-gray-600 rounded", 17 | "outline-none focus:border-indigo-600 focus:shadow-md", 18 | "transition easy-in-out duration-300", 19 | { 20 | "w-full": fullWidth, 21 | }, 22 | className, 23 | ]); 24 | 25 | return ( 26 | 27 | ); 28 | } 29 | 30 | TextField.defaultProps = { 31 | type: "text", 32 | disabled: false, 33 | fullWidth: false, 34 | className: "", 35 | }; 36 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import * as serviceWorker from "./serviceWorker"; 4 | import { NhostAuthProvider, NhostApolloProvider } from "react-nhost"; 5 | import { auth } from "utils/nhost"; 6 | import { Router } from "components/routers/router"; 7 | import { GRAPHQL_ENDPOINT } from "utils/config"; 8 | import "styles/app.css"; 9 | 10 | ReactDOM.render( 11 | 12 | 13 | 14 | 15 | 16 | 17 | , 18 | document.getElementById("root") 19 | ); 20 | 21 | // If you want your app to work offline and load faster, you can change 22 | // unregister() to register() below. Note this comes with some pitfalls. 23 | // Learn more about service workers: https://bit.ly/CRA-PWA 24 | serviceWorker.unregister(); 25 | -------------------------------------------------------------------------------- /src/components/routers/router-app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; 3 | import { Dashboard } from "components/app/dashboard"; 4 | import { Todos } from "components/app/todos"; 5 | import { Files } from "components/app/files"; 6 | import { Settings } from "components/app/settings"; 7 | import { Layout } from "components/layout"; 8 | 9 | export function RouterApp() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/components/svg/loading.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import classNames from "classnames"; 3 | 4 | type SvgLoadingProps = { 5 | className?: string; 6 | }; 7 | 8 | export function SvgLoading(props: SvgLoadingProps) { 9 | const { className } = props; 10 | 11 | const classes = classNames("inline", className); 12 | 13 | return ( 14 | 20 | 24 | 33 | 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/generated/s_userGetSelf.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // @generated 4 | // This file was automatically generated and should not be edited. 5 | 6 | // ==================================================== 7 | // GraphQL subscription operation: s_userGetSelf 8 | // ==================================================== 9 | 10 | export interface s_userGetSelf_users_by_pk_account { 11 | __typename: "auth_accounts"; 12 | email: gql_citext | null; 13 | } 14 | 15 | export interface s_userGetSelf_users_by_pk { 16 | __typename: "users"; 17 | id: gql_uuid; 18 | display_name: string | null; 19 | avatar_url: string | null; 20 | /** 21 | * An object relationship 22 | */ 23 | account: s_userGetSelf_users_by_pk_account | null; 24 | } 25 | 26 | export interface s_userGetSelf { 27 | /** 28 | * fetch data from the table: "users" using primary key columns 29 | */ 30 | users_by_pk: s_userGetSelf_users_by_pk | null; 31 | } 32 | 33 | export interface s_userGetSelfVariables { 34 | id: gql_uuid; 35 | } 36 | -------------------------------------------------------------------------------- /src/gql/todos.ts: -------------------------------------------------------------------------------- 1 | import gql from "graphql-tag"; 2 | 3 | export const GET_TODOS = gql` 4 | query getTodos($limit: Int!) { 5 | todos(limit: $limit) { 6 | id 7 | todo 8 | done 9 | updated_at 10 | } 11 | } 12 | `; 13 | 14 | export const S_GET_TODOS = gql` 15 | subscription s_getTodos($limit: Int!) { 16 | todos(limit: $limit, order_by: { created_at: desc }) { 17 | id 18 | todo 19 | done 20 | updated_at 21 | } 22 | } 23 | `; 24 | 25 | export const INSERT_TODO = gql` 26 | mutation insertTodo($todo: todos_insert_input!) { 27 | insert_todos_one(object: $todo) { 28 | id 29 | } 30 | } 31 | `; 32 | 33 | export const UPDATE_TODO = gql` 34 | mutation updateTodo($id: uuid!, $todo: todos_set_input!) { 35 | update_todos_by_pk(pk_columns: { id: $id }, _set: $todo) { 36 | id 37 | todo 38 | done 39 | } 40 | } 41 | `; 42 | 43 | export const DELETE_TODOS = gql` 44 | mutation deleteTodos($where: todos_bool_exp!) { 45 | delete_todos(where: $where) { 46 | affected_rows 47 | } 48 | } 49 | `; 50 | -------------------------------------------------------------------------------- /src/components/routers/router.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter, Route, Switch } from "react-router-dom"; 3 | import { Register } from "components/register"; 4 | import { Login } from "components/login"; 5 | import { NewEmail } from "components/new-email"; 6 | import { PasswordSet } from "components/password-set"; 7 | import { PasswordForgot } from "components/password-forgot"; 8 | 9 | import { RouterApp } from "components/routers/router-app"; 10 | import { AuthGate } from "components/auth-gate"; 11 | 12 | export function Router() { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /hasura/migrations/100_init_example_app/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE public.files ( 2 | id uuid DEFAULT public.gen_random_uuid() NOT NULL, 3 | created_at timestamp with time zone DEFAULT now() NOT NULL, 4 | file_path text NOT NULL, 5 | downloadable_url text NOT NULL, 6 | user_id uuid NOT NULL 7 | ); 8 | CREATE TABLE public.todos ( 9 | id uuid DEFAULT public.gen_random_uuid() NOT NULL, 10 | created_at timestamp with time zone DEFAULT now() NOT NULL, 11 | updated_at timestamp with time zone DEFAULT now() NOT NULL, 12 | user_id uuid NOT NULL, 13 | todo text NOT NULL, 14 | done boolean DEFAULT false NOT NULL 15 | ); 16 | ALTER TABLE ONLY public.files 17 | ADD CONSTRAINT files_pkey PRIMARY KEY (id); 18 | ALTER TABLE ONLY public.todos 19 | ADD CONSTRAINT todos_pkey PRIMARY KEY (id); 20 | CREATE TRIGGER set_public_todos_updated_at BEFORE UPDATE ON public.todos FOR EACH ROW EXECUTE FUNCTION public.set_current_timestamp_updated_at(); 21 | COMMENT ON TRIGGER set_public_todos_updated_at ON public.todos IS 'trigger to set value of column "updated_at" to current timestamp on row update'; 22 | ALTER TABLE ONLY public.files 23 | ADD CONSTRAINT files_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON UPDATE RESTRICT ON DELETE CASCADE; 24 | ALTER TABLE ONLY public.todos 25 | ADD CONSTRAINT todos_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON UPDATE RESTRICT ON DELETE CASCADE; 26 | -------------------------------------------------------------------------------- /src/components/app/header-user.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useHistory } from "react-router-dom"; 3 | import { useSubscription } from "@apollo/client"; 4 | import { S_USER_GET_SELF } from "gql/users"; 5 | import { s_userGetSelf } from "generated/s_userGetSelf"; 6 | import { auth } from "utils/nhost"; 7 | 8 | export interface IHeaderUserProps {} 9 | 10 | export function HeaderUser() { 11 | const history = useHistory(); 12 | 13 | const user_id = auth.getClaim("x-hasura-user-id"); 14 | 15 | const { loading, error, data } = useSubscription( 16 | S_USER_GET_SELF, 17 | { 18 | variables: { 19 | id: user_id, 20 | }, 21 | } 22 | ); 23 | 24 | if (loading) return
Loading..
; 25 | if (!data?.users_by_pk || error) { 26 | return
Unable to load user
; 27 | } 28 | 29 | const user = data.users_by_pk; 30 | 31 | return ( 32 |
33 | {user.avatar_url ? ( 34 | {`Avatar`} 39 | ) : ( 40 | // 41 |
42 | )} 43 |
44 | {user.display_name} 45 | { 48 | auth.logout(); 49 | history.push("/"); 50 | }} 51 | > 52 | Logout 53 | 54 |
55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/components/app/todos-form.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { TextField } from "components/ui"; 3 | import { INSERT_TODO } from "gql/todos"; 4 | import { useMutation } from "@apollo/client"; 5 | 6 | export interface ITodosFormProps {} 7 | 8 | export function TodosForm(props: ITodosFormProps) { 9 | const [todoInput, setTodoInput] = useState(""); 10 | 11 | const [ 12 | addTodo, 13 | { loading: mutationLoading, error: mutationError }, 14 | ] = useMutation(INSERT_TODO); 15 | 16 | const handleSubmit = async (e: React.FormEvent) => { 17 | e.preventDefault(); 18 | 19 | try { 20 | await addTodo({ 21 | variables: { 22 | todo: { 23 | todo: todoInput, 24 | }, 25 | }, 26 | }); 27 | } catch (error) { 28 | // error, we'll catch this error in `mutationError`. 29 | } 30 | 31 | setTodoInput(""); 32 | }; 33 | 34 | return ( 35 |
36 |
37 | setTodoInput(e.target.value)} 47 | value={todoInput} 48 | disabled={mutationLoading} 49 | /> 50 | 51 | 52 | {mutationError && ( 53 |
54 |
Error:
55 | {mutationError.message} 56 |
57 | )} 58 |
59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Nhost Example App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/app/header.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link, useRouteMatch } from "react-router-dom"; 3 | import classNames from "classnames"; 4 | import { HeaderUser } from "./header-user"; 5 | 6 | export function Header() { 7 | const dashboard_match = useRouteMatch({ 8 | path: "/", 9 | exact: true, 10 | strict: false, 11 | }); 12 | 13 | const todo_match = useRouteMatch({ 14 | path: "/todos", 15 | exact: true, 16 | strict: false, 17 | }); 18 | 19 | const files_match = useRouteMatch({ 20 | path: "/files", 21 | exact: false, 22 | }); 23 | 24 | const settings_match = useRouteMatch({ 25 | path: "/settings", 26 | exact: false, 27 | }); 28 | 29 | const dashboard_classes = classNames({ 30 | "block mr-3 pb-4 text-gray-600 transition easy-in-out duration-200": true, 31 | "border-b-2 border-indigo-600 text-gray-900": dashboard_match, 32 | }); 33 | 34 | const todo_classes = classNames({ 35 | "block mx-3 pb-4 text-gray-600 transition easy-in-out duration-200": true, 36 | "border-b-2 border-indigo-600 text-gray-900": todo_match, 37 | }); 38 | 39 | const files_classes = classNames({ 40 | "block mx-3 pb-4 text-gray-600 transition easy-in-out duration-200": true, 41 | "border-b-2 border-indigo-800": files_match, 42 | }); 43 | 44 | const settings_classes = classNames({ 45 | "block mx-3 pb-4 text-gray-600 transition easy-in-out duration-200": true, 46 | "border-b-2 border-indigo-800": settings_match, 47 | }); 48 | 49 | return ( 50 |
51 |
52 | 53 |
54 | 55 |
56 |
57 | 58 | Dashboard 59 | 60 | 61 | 62 | Todos 63 | 64 | 65 | 66 | Files 67 | 68 | 69 | 70 | Settings 71 | 72 |
73 |
74 |
75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nhost-react-example-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@apollo/client": "^3.1.3", 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.3.2", 9 | "@testing-library/user-event": "^7.1.2", 10 | "@types/classnames": "^2.2.10", 11 | "@types/jest": "^24.0.0", 12 | "@types/node": "^12.0.0", 13 | "@types/react": "^16.9.0", 14 | "@types/react-dom": "^16.9.0", 15 | "@types/react-router-dom": "^5.1.5", 16 | "@types/uuid": "^8.0.0", 17 | "classnames": "^2.2.6", 18 | "date-fns": "^2.14.0", 19 | "graphql": "^15.3.0", 20 | "graphql-tag": "^2.11.0", 21 | "nhost-js-sdk": "2.1.7", 22 | "react": "^16.13.1", 23 | "react-dom": "^16.13.1", 24 | "react-hook-form": "^5.7.2", 25 | "react-nhost": "^1.1.10", 26 | "react-router-dom": "^5.2.0", 27 | "react-scripts": "3.4.1", 28 | "tailwindcss": "^1.7.5", 29 | "typescript": "~3.7.2", 30 | "uuid": "^8.1.0" 31 | }, 32 | "resolutions": { 33 | "graphql": "^14.0.0" 34 | }, 35 | "scripts": { 36 | "start": "npm-run-all -p react:start watch:css", 37 | "react:start": "react-scripts start", 38 | "build": "yarn run build:css && react-scripts build", 39 | "test": "react-scripts test", 40 | "eject": "react-scripts eject", 41 | "apollo:generate": "apollo codegen:generate --target typescript --excludes=node_modules/* --includes=\"src/**/*.{ts,tsx}\" --endpoint http://localhost:8080/v1/graphql --header \"x-hasura-admin-secret: hejsan\" --tagName=gql --passthroughCustomScalars --customScalarsPrefix gql_ --outputFlat src/generated", 42 | "watch:css": "postcss src/styles/tailwind.css -o src/styles/app.css -w", 43 | "build:css": "postcss src/styles/tailwind.css -o src/styles/app.css" 44 | }, 45 | "eslintConfig": { 46 | "extends": "react-app" 47 | }, 48 | "browserslist": { 49 | "production": [ 50 | ">0.2%", 51 | "not dead", 52 | "not op_mini all" 53 | ], 54 | "development": [ 55 | "last 1 chrome version", 56 | "last 1 firefox version", 57 | "last 1 safari version" 58 | ] 59 | }, 60 | "devDependencies": { 61 | "autoprefixer": "^9.8.6", 62 | "npm-run-all": "^4.1.5", 63 | "postcss-cli": "^7.1.1" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/password-set.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useParams, Link } from "react-router-dom"; 3 | import { Button, TextField } from "components/ui"; 4 | import { auth } from "utils/nhost"; 5 | import { SvgLoading } from "components/svg"; 6 | 7 | export function PasswordSet() { 8 | const [password, setPassword] = useState(""); 9 | const [loading, setLoading] = useState(false); 10 | const [error, setError] = useState(""); 11 | const [completed, setCompleted] = useState(false); 12 | 13 | const { ticket } = useParams(); 14 | 15 | const handleSubmit = async (e: React.FormEvent) => { 16 | e.preventDefault(); 17 | setLoading(true); 18 | 19 | try { 20 | await auth.changePasswordChange(password, ticket); 21 | } catch (err) { 22 | try { 23 | setError(err.response.data.message); 24 | } catch (error) { 25 | setError(err.message); 26 | } 27 | return; 28 | } finally { 29 | setLoading(false); 30 | } 31 | 32 | setLoading(false); 33 | setCompleted(true); 34 | }; 35 | 36 | if (completed) { 37 | return ( 38 |
39 | Password updated! Now Sign In! 40 |
41 | ); 42 | } 43 | 44 | return ( 45 |
46 |

Request new password

47 |
48 |
49 | ) => 59 | setPassword(e.target.value) 60 | } 61 | /> 62 | 63 |
64 | 74 |
75 |
76 |
77 | {error &&
{error}
} 78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /src/components/password-forgot.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { Button, TextField } from "components/ui"; 4 | import { auth } from "utils/nhost"; 5 | import { SvgLoading } from "components/svg"; 6 | 7 | export function PasswordForgot() { 8 | const [email, setEmail] = useState(""); 9 | const [loading, setLoading] = useState(false); 10 | const [error, setError] = useState(""); 11 | const [completed, setCompleted] = useState(false); 12 | 13 | const handleSubmit = async (e: React.FormEvent) => { 14 | e.preventDefault(); 15 | setLoading(true); 16 | 17 | try { 18 | await auth.changePasswordRequest(email); 19 | } catch (err) { 20 | try { 21 | setError(err.response.data.message); 22 | } catch (error) { 23 | setError(err.message); 24 | } 25 | return; 26 | } finally { 27 | setLoading(false); 28 | } 29 | 30 | setLoading(false); 31 | setCompleted(true); 32 | }; 33 | 34 | if (completed) { 35 | return ( 36 |
37 | We have sent you an email to change your password. 38 |
39 | You'll get a ticket in an email to the 40 | email addres. Use the ticket as this url:{" "} 41 | 42 | http://localhost:3000/password-set/ 43 | {""} 44 | 45 | . 46 |
47 |
48 | You can change your e-mail templates to match this new url. Read more 49 | here:{" "} 50 | 56 | https://docs.nhost.io/auth/email-templates 57 | 58 | . 59 |
60 |
61 | ); 62 | } 63 | 64 | return ( 65 |
66 |

Request new password

67 |
68 |
69 | ) => 79 | setEmail(e.target.value) 80 | } 81 | /> 82 | 83 |
84 | 94 |
95 |
96 |
97 | {error &&
{error}
} 98 | 99 |
100 |
101 | Already have an account?{" "} 102 | 103 | Sign In! 104 | 105 |
106 |
107 | Don't have an account?{" "} 108 | 109 | Sign Up! 110 | 111 |
112 |
113 |
114 | ); 115 | } 116 | 117 | export interface IPasswordForgotProps {} 118 | -------------------------------------------------------------------------------- /src/components/login.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link, useHistory } from "react-router-dom"; 3 | 4 | import { BACKEND_ENDPOINT } from "utils/config"; 5 | import { auth } from "utils/nhost"; 6 | import { Button, TextField } from "components/ui"; 7 | import github from "images/github.png"; 8 | import google from "images/google.png"; 9 | import facebook from "images/facebook.png"; 10 | import { SvgLoading } from "components/svg"; 11 | 12 | export function Login() { 13 | const [email, setEmail] = useState(""); 14 | const [password, setPassword] = useState(""); 15 | const [loading, setLoading] = useState(false); 16 | const history = useHistory(); 17 | 18 | const [error, setError] = useState(""); 19 | 20 | const handleSubmit = async (e: React.FormEvent) => { 21 | e.preventDefault(); 22 | setLoading(true); 23 | 24 | try { 25 | await auth.login(email, password); 26 | } catch (err) { 27 | try { 28 | setError(err.response.data.message); 29 | } catch (error) { 30 | setError(err.message); 31 | } 32 | return; 33 | } finally { 34 | setLoading(false); 35 | } 36 | 37 | setLoading(false); 38 | history.push("/"); 39 | }; 40 | 41 | return ( 42 |
43 |

Nhost Example Login

44 |
45 |
46 | ) => 56 | setEmail(e.target.value) 57 | } 58 | /> 59 | 60 |
61 | ) => 70 | setPassword(e.target.value) 71 | } 72 | /> 73 |
74 | 75 |
76 | 86 |
87 |
88 |
89 | {error &&
{error}
} 90 | {/*
OR SIGN IN WITH
*/} 91 | 92 |
93 | or sign in with 94 |
95 | 124 |
125 |
126 | Don't have an account?{" "} 127 | 128 | Sign Up! 129 | 130 |
131 |
132 | 133 | Forgot password 134 | 135 |
136 |
137 |
138 | ); 139 | } 140 | -------------------------------------------------------------------------------- /hasura/metadata/tables.yaml: -------------------------------------------------------------------------------- 1 | - table: 2 | schema: auth 3 | name: account_providers 4 | object_relationships: 5 | - name: account 6 | using: 7 | foreign_key_constraint_on: account_id 8 | - name: provider 9 | using: 10 | foreign_key_constraint_on: auth_provider 11 | - table: 12 | schema: auth 13 | name: account_roles 14 | object_relationships: 15 | - name: account 16 | using: 17 | foreign_key_constraint_on: account_id 18 | - name: roleByRole 19 | using: 20 | foreign_key_constraint_on: role 21 | - table: 22 | schema: auth 23 | name: accounts 24 | object_relationships: 25 | - name: role 26 | using: 27 | foreign_key_constraint_on: default_role 28 | - name: user 29 | using: 30 | foreign_key_constraint_on: user_id 31 | array_relationships: 32 | - name: account_providers 33 | using: 34 | foreign_key_constraint_on: 35 | column: account_id 36 | table: 37 | schema: auth 38 | name: account_providers 39 | - name: account_roles 40 | using: 41 | foreign_key_constraint_on: 42 | column: account_id 43 | table: 44 | schema: auth 45 | name: account_roles 46 | - name: refresh_tokens 47 | using: 48 | foreign_key_constraint_on: 49 | column: account_id 50 | table: 51 | schema: auth 52 | name: refresh_tokens 53 | select_permissions: 54 | - role: user 55 | permission: 56 | columns: 57 | - email 58 | filter: 59 | user_id: 60 | _eq: X-Hasura-User-Id 61 | - table: 62 | schema: auth 63 | name: providers 64 | array_relationships: 65 | - name: account_providers 66 | using: 67 | foreign_key_constraint_on: 68 | column: auth_provider 69 | table: 70 | schema: auth 71 | name: account_providers 72 | - table: 73 | schema: auth 74 | name: refresh_tokens 75 | object_relationships: 76 | - name: account 77 | using: 78 | foreign_key_constraint_on: account_id 79 | - table: 80 | schema: auth 81 | name: roles 82 | array_relationships: 83 | - name: account_roles 84 | using: 85 | foreign_key_constraint_on: 86 | column: role 87 | table: 88 | schema: auth 89 | name: account_roles 90 | - name: accounts 91 | using: 92 | foreign_key_constraint_on: 93 | column: default_role 94 | table: 95 | schema: auth 96 | name: accounts 97 | - table: 98 | schema: public 99 | name: files 100 | object_relationships: 101 | - name: user 102 | using: 103 | foreign_key_constraint_on: user_id 104 | insert_permissions: 105 | - role: user 106 | permission: 107 | check: {} 108 | set: 109 | user_id: x-hasura-user-id 110 | columns: 111 | - downloadable_url 112 | - file_path 113 | backend_only: false 114 | select_permissions: 115 | - role: user 116 | permission: 117 | columns: 118 | - downloadable_url 119 | - file_path 120 | - created_at 121 | - id 122 | - user_id 123 | filter: 124 | user_id: 125 | _eq: X-Hasura-User-Id 126 | delete_permissions: 127 | - role: user 128 | permission: 129 | filter: 130 | user_id: 131 | _eq: X-Hasura-User-Id 132 | - table: 133 | schema: public 134 | name: todos 135 | object_relationships: 136 | - name: user 137 | using: 138 | foreign_key_constraint_on: user_id 139 | insert_permissions: 140 | - role: user 141 | permission: 142 | check: {} 143 | set: 144 | user_id: x-hasura-user-id 145 | columns: 146 | - done 147 | - todo 148 | backend_only: false 149 | select_permissions: 150 | - role: user 151 | permission: 152 | columns: 153 | - done 154 | - todo 155 | - created_at 156 | - updated_at 157 | - id 158 | - user_id 159 | filter: 160 | user_id: 161 | _eq: X-Hasura-User-Id 162 | update_permissions: 163 | - role: user 164 | permission: 165 | columns: 166 | - done 167 | - todo 168 | filter: 169 | user_id: 170 | _eq: X-Hasura-User-Id 171 | check: null 172 | delete_permissions: 173 | - role: user 174 | permission: 175 | filter: 176 | user_id: 177 | _eq: X-Hasura-User-Id 178 | - table: 179 | schema: public 180 | name: users 181 | object_relationships: 182 | - name: account 183 | using: 184 | manual_configuration: 185 | remote_table: 186 | schema: auth 187 | name: accounts 188 | column_mapping: 189 | id: user_id 190 | array_relationships: 191 | - name: files 192 | using: 193 | foreign_key_constraint_on: 194 | column: user_id 195 | table: 196 | schema: public 197 | name: files 198 | - name: todos 199 | using: 200 | foreign_key_constraint_on: 201 | column: user_id 202 | table: 203 | schema: public 204 | name: todos 205 | select_permissions: 206 | - role: user 207 | permission: 208 | columns: 209 | - id 210 | - created_at 211 | - updated_at 212 | - display_name 213 | - avatar_url 214 | filter: 215 | id: 216 | _eq: X-Hasura-User-Id 217 | -------------------------------------------------------------------------------- /src/components/register.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link, useHistory } from "react-router-dom"; 3 | 4 | import { BACKEND_ENDPOINT } from "utils/config"; 5 | import { auth } from "utils/nhost"; 6 | import { Button, TextField } from "components/ui"; 7 | import github from "images/github.png"; 8 | import google from "images/google.png"; 9 | import facebook from "images/facebook.png"; 10 | import { SvgLoading } from "components/svg"; 11 | 12 | export function Register() { 13 | const [name, setName] = useState(""); 14 | const [email, setEmail] = useState(""); 15 | const [password, setPassword] = useState(""); 16 | const [loading, setLoading] = useState(false); 17 | const history = useHistory(); 18 | 19 | const [error, setError] = useState(""); 20 | 21 | const handleSubmit = async (e: React.FormEvent) => { 22 | e.preventDefault(); 23 | setLoading(true); 24 | 25 | try { 26 | await auth.register(email, password, { 27 | display_name: name, 28 | }); 29 | } catch (err) { 30 | try { 31 | setError(err.response.data.message); 32 | } catch (error) { 33 | setError(err.message); 34 | } 35 | } finally { 36 | setLoading(false); 37 | } 38 | 39 | setLoading(false); 40 | 41 | await auth.register(email, password); 42 | history.push("/"); 43 | }; 44 | 45 | return ( 46 |
47 |

48 | Nhost Example Register 49 |

50 |
51 |
52 | ) => 62 | setName(e.target.value) 63 | } 64 | /> 65 | 66 | ) => 75 | setEmail(e.target.value) 76 | } 77 | /> 78 | 79 |
80 | ) => 89 | setPassword(e.target.value) 90 | } 91 | /> 92 |
93 | 94 |
95 | 105 |
106 |
107 |
108 | {error &&
{error}
} 109 | {/*
OR SIGN IN WITH
*/} 110 | 111 |
112 | or sign up with 113 |
114 | 143 |
144 |
145 | Already have an account?{" "} 146 | 147 | Sign In! 148 | 149 |
150 |
151 | 152 | Forgot password 153 | 154 |
155 |
156 |
157 | ); 158 | } 159 | -------------------------------------------------------------------------------- /src/serviceWorker.ts: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | type Config = { 24 | onSuccess?: (registration: ServiceWorkerRegistration) => void; 25 | onUpdate?: (registration: ServiceWorkerRegistration) => void; 26 | }; 27 | 28 | export function register(config?: Config) { 29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 30 | // The URL constructor is available in all browsers that support SW. 31 | const publicUrl = new URL( 32 | process.env.PUBLIC_URL, 33 | window.location.href 34 | ); 35 | if (publicUrl.origin !== window.location.origin) { 36 | // Our service worker won't work if PUBLIC_URL is on a different origin 37 | // from what our page is served on. This might happen if a CDN is used to 38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 39 | return; 40 | } 41 | 42 | window.addEventListener('load', () => { 43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 44 | 45 | if (isLocalhost) { 46 | // This is running on localhost. Let's check if a service worker still exists or not. 47 | checkValidServiceWorker(swUrl, config); 48 | 49 | // Add some additional logging to localhost, pointing developers to the 50 | // service worker/PWA documentation. 51 | navigator.serviceWorker.ready.then(() => { 52 | console.log( 53 | 'This web app is being served cache-first by a service ' + 54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 55 | ); 56 | }); 57 | } else { 58 | // Is not localhost. Just register service worker 59 | registerValidSW(swUrl, config); 60 | } 61 | }); 62 | } 63 | } 64 | 65 | function registerValidSW(swUrl: string, config?: Config) { 66 | navigator.serviceWorker 67 | .register(swUrl) 68 | .then(registration => { 69 | registration.onupdatefound = () => { 70 | const installingWorker = registration.installing; 71 | if (installingWorker == null) { 72 | return; 73 | } 74 | installingWorker.onstatechange = () => { 75 | if (installingWorker.state === 'installed') { 76 | if (navigator.serviceWorker.controller) { 77 | // At this point, the updated precached content has been fetched, 78 | // but the previous service worker will still serve the older 79 | // content until all client tabs are closed. 80 | console.log( 81 | 'New content is available and will be used when all ' + 82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 83 | ); 84 | 85 | // Execute callback 86 | if (config && config.onUpdate) { 87 | config.onUpdate(registration); 88 | } 89 | } else { 90 | // At this point, everything has been precached. 91 | // It's the perfect time to display a 92 | // "Content is cached for offline use." message. 93 | console.log('Content is cached for offline use.'); 94 | 95 | // Execute callback 96 | if (config && config.onSuccess) { 97 | config.onSuccess(registration); 98 | } 99 | } 100 | } 101 | }; 102 | }; 103 | }) 104 | .catch(error => { 105 | console.error('Error during service worker registration:', error); 106 | }); 107 | } 108 | 109 | function checkValidServiceWorker(swUrl: string, config?: Config) { 110 | // Check if the service worker can be found. If it can't reload the page. 111 | fetch(swUrl, { 112 | headers: { 'Service-Worker': 'script' } 113 | }) 114 | .then(response => { 115 | // Ensure service worker exists, and that we really are getting a JS file. 116 | const contentType = response.headers.get('content-type'); 117 | if ( 118 | response.status === 404 || 119 | (contentType != null && contentType.indexOf('javascript') === -1) 120 | ) { 121 | // No service worker found. Probably a different app. Reload the page. 122 | navigator.serviceWorker.ready.then(registration => { 123 | registration.unregister().then(() => { 124 | window.location.reload(); 125 | }); 126 | }); 127 | } else { 128 | // Service worker found. Proceed as normal. 129 | registerValidSW(swUrl, config); 130 | } 131 | }) 132 | .catch(() => { 133 | console.log( 134 | 'No internet connection found. App is running in offline mode.' 135 | ); 136 | }); 137 | } 138 | 139 | export function unregister() { 140 | if ('serviceWorker' in navigator) { 141 | navigator.serviceWorker.ready 142 | .then(registration => { 143 | registration.unregister(); 144 | }) 145 | .catch(error => { 146 | console.error(error.message); 147 | }); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/components/app/settings.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useSubscription } from "@apollo/client"; 3 | import { TextField, Button } from "components/ui"; 4 | import { auth } from "utils/nhost"; 5 | import { S_USER_GET_SELF } from "gql/users"; 6 | import { s_userGetSelf } from "generated/s_userGetSelf"; 7 | 8 | function SettingsCurrent() { 9 | const user_id = auth.getClaim("x-hasura-user-id"); 10 | const { loading, error, data } = useSubscription( 11 | S_USER_GET_SELF, 12 | { 13 | variables: { 14 | id: user_id, 15 | }, 16 | } 17 | ); 18 | 19 | if (loading) { 20 | return
Loading..
; 21 | } 22 | 23 | if (error || !data?.users_by_pk) { 24 | return
Unable to load settings
; 25 | } 26 | 27 | return
Email: {data.users_by_pk.account?.email}
; 28 | } 29 | 30 | function SettingsNewEmail() { 31 | const [email, setEmail] = useState(""); 32 | const [error, setError] = useState(""); 33 | const [loading, setLoading] = useState(false); 34 | 35 | // const { enqueueSnackbar } = useSnackbar(); 36 | 37 | async function handleSubmit(e: any) { 38 | e.preventDefault(); 39 | 40 | try { 41 | await auth.changeEmailRequest(email); 42 | } catch (err) { 43 | try { 44 | setError(err.response.data.message); 45 | } catch (error) { 46 | setError(err.message); 47 | } 48 | return; 49 | } finally { 50 | setLoading(false); 51 | } 52 | 53 | setEmail(""); 54 | } 55 | 56 | return ( 57 |
58 |
New email
59 |
60 | } }) => 67 | setEmail(e.target.value) 68 | } 69 | value={email} 70 | /> 71 | 80 | 81 | {error &&
{error}
} 82 |
83 | You'll get a ticket in an email to the new 84 | email addres. Use the ticket as this url:{" "} 85 | 86 | http://localhost:3000/new-email/ 87 | {""} 88 | 89 | . 90 |
91 |
92 | You can change your e-mail templates to match this new url. Read more 93 | here:{" "} 94 | 100 | https://docs.nhost.io/auth/email-templates 101 | 102 | . 103 |
104 |
105 | ); 106 | } 107 | 108 | function SettingsNewPassword() { 109 | const [oldPassword, setOldPassword] = useState(""); 110 | const [newPassword, setNewPassword] = useState(""); 111 | const [error, setError] = useState(""); 112 | const [loading, setLoading] = useState(false); 113 | 114 | // const { enqueueSnackbar } = useSnackbar(); 115 | 116 | async function handleSubmit(e: any) { 117 | e.preventDefault(); 118 | 119 | setLoading(true); 120 | 121 | try { 122 | await auth.changePassword(oldPassword, newPassword); 123 | } catch (err) { 124 | try { 125 | setError(err.response.data.message); 126 | } catch (error) { 127 | setError(err.message); 128 | } 129 | return; 130 | } finally { 131 | setLoading(false); 132 | } 133 | 134 | setOldPassword(""); 135 | setNewPassword(""); 136 | 137 | // return enqueueSnackbar("New password set", { 138 | // variant: "success", 139 | // }); 140 | } 141 | 142 | return ( 143 |
144 |
New Password
145 |
146 | } }) => 153 | setOldPassword(e.target.value) 154 | } 155 | value={oldPassword} 156 | /> 157 | 158 | } }) => 165 | setNewPassword(e.target.value) 166 | } 167 | value={newPassword} 168 | /> 169 | 170 | 178 | 179 | {error &&
{error}
} 180 | 181 |
182 | ); 183 | } 184 | 185 | export interface ISettingsProps {} 186 | 187 | export function Settings(props: ISettingsProps) { 188 | return ( 189 |
190 |
191 |
Current settings:
192 | 193 |
194 | 195 | 196 |
197 | ); 198 | } 199 | -------------------------------------------------------------------------------- /src/components/app/todos.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useSubscription, useMutation } from "@apollo/client"; 3 | import classNames from "classnames"; 4 | 5 | import { S_GET_TODOS, UPDATE_TODO, DELETE_TODOS } from "gql/todos"; 6 | import { s_getTodos, s_getTodosVariables } from "generated/s_getTodos"; 7 | import { TodosForm } from "./todos-form"; 8 | import { getTodos_todos } from "generated/getTodos"; 9 | 10 | export interface ITodosProps {} 11 | 12 | export function Todos(props: ITodosProps) { 13 | const [filter, setFilter] = useState("all"); 14 | 15 | const { loading, data } = useSubscription( 16 | S_GET_TODOS, 17 | { 18 | variables: { limit: 123 }, 19 | } 20 | ); 21 | const [updateTodo] = useMutation(UPDATE_TODO); 22 | const [deleteTodos] = useMutation(DELETE_TODOS); 23 | 24 | const toggleTodoItem = (todo: getTodos_todos) => { 25 | updateTodo({ 26 | variables: { 27 | id: todo.id, 28 | todo: { 29 | done: !todo.done, 30 | }, 31 | }, 32 | }); 33 | }; 34 | 35 | const renderTodos = () => { 36 | if (loading) { 37 | return
Loading...
; 38 | } 39 | 40 | if (!data || data.todos.length === 0) { 41 | return ( 42 |
43 | No todos. Create one! 44 |
45 | ); 46 | } 47 | 48 | const { todos } = data; 49 | 50 | let todos_left = 0; 51 | todos.forEach((todo) => { 52 | if (!todo.done) todos_left++; 53 | }); 54 | 55 | let todos_filtered; 56 | if (filter === "active") { 57 | todos_filtered = todos.filter((todo) => { 58 | return todo.done === false; 59 | }); 60 | } else if (filter === "completed") { 61 | todos_filtered = todos.filter((todo) => { 62 | return todo.done === true; 63 | }); 64 | } else { 65 | // if (filter === "all") { 66 | todos_filtered = todos; 67 | } 68 | 69 | const buttonFilterAll = classNames([ 70 | "py-1 px-2 mx-3 outline-none", 71 | { 72 | border: filter === "all", 73 | }, 74 | ]); 75 | 76 | const buttonFilterActive = classNames([ 77 | "py-1 px-2 mx-3 outline-none", 78 | { 79 | border: filter === "active", 80 | }, 81 | ]); 82 | 83 | const buttonFilterCompleted = classNames([ 84 | "py-1 px-2 mx-3 outline-none", 85 | { 86 | border: filter === "completed", 87 | }, 88 | ]); 89 | 90 | return ( 91 |
92 | {todos_filtered.map((todo) => { 93 | const checkboxClasses = classNames([ 94 | "flex items-center justify-center", 95 | "w-6", 96 | "h-6", 97 | "rounded-full", 98 | "border", 99 | "cursor-pointer", 100 | "hover:shadow-lg", 101 | "transition easy-in-out duration-150", 102 | { 103 | "text-teal-400": todo.done, 104 | "border-teal-400": todo.done, 105 | }, 106 | ]); 107 | 108 | const textClasses = classNames([ 109 | "pl-4", 110 | "transition easy-in-out duration-150", 111 | { 112 | "text-gray-600": todo.done, 113 | "line-through": todo.done, 114 | }, 115 | ]); 116 | 117 | return ( 118 |
119 |
{ 122 | toggleTodoItem(todo); 123 | }} 124 | > 125 | {todo.done && ( 126 | 132 | 138 | 139 | )} 140 |
141 | {/* {todo.done ? done : not done} */} 142 |
{todo.todo}
143 |
144 | ); 145 | })} 146 | 147 |
148 |
{todos_left} items left
149 |
150 | 158 | 166 | 174 |
175 |
176 | 192 |
193 |
194 |
195 | ); 196 | }; 197 | 198 | return ( 199 |
200 | 201 | {renderTodos()} 202 |
203 | ); 204 | } 205 | -------------------------------------------------------------------------------- /src/components/app/files.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef } from "react"; 2 | import { v4 as uuidv4 } from "uuid"; 3 | import { formatDistanceToNowStrict } from "date-fns"; 4 | import { useMutation, useSubscription } from "@apollo/client"; 5 | import { INSERT_FILE, S_GET_FILES, DELETE_FILES } from "gql/files"; 6 | import { s_getFiles, s_getFilesVariables } from "generated/s_getFiles"; 7 | import { storage } from "utils/nhost"; 8 | import { BACKEND_ENDPOINT } from "utils/config"; 9 | import { Button } from "components/ui"; 10 | 11 | export function FilesList() { 12 | const { loading, data } = useSubscription( 13 | S_GET_FILES, 14 | { 15 | variables: { limit: 40 }, 16 | } 17 | ); 18 | 19 | const [ 20 | deleteFile, 21 | // { loading: mutationLoading, error: mutationError }, 22 | ] = useMutation(DELETE_FILES); 23 | 24 | const [forceUpdateValue, forceUpdate] = React.useState(0); 25 | 26 | // rerender UI every 5 sec to update 'created at' column 27 | React.useEffect(() => { 28 | const interval = setInterval(() => { 29 | forceUpdate(forceUpdateValue + 1); 30 | }, 5000); 31 | 32 | return () => clearInterval(interval); 33 | }, [forceUpdateValue]); 34 | 35 | if (loading) { 36 | return
Loading...
; 37 | } 38 | 39 | if (!data || data.files.length === 0) { 40 | return
No files.
; 41 | } 42 | 43 | const { files } = data; 44 | 45 | return ( 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {files.map((file) => { 56 | return ( 57 | 58 | 68 | 73 | 85 | 104 | 105 | ); 106 | })} 107 | 108 |
NameCreated
59 | 65 | {file.file_path} 66 | 67 | 69 | {formatDistanceToNowStrict(new Date(file.created_at), { 70 | addSuffix: true, 71 | })} 72 | 74 | 84 | 86 | 103 |
109 | ); 110 | } 111 | 112 | export interface IFilesProps {} 113 | 114 | export function Files(props: IFilesProps) { 115 | const fileInput = useRef(null); 116 | const [fileData, setFileData] = useState(); 117 | const [uploadState, setUploadState] = useState(""); 118 | const [uploadCompleted, setUploadCompleted] = useState(0); 119 | 120 | console.log("inside files component"); 121 | 122 | const [ 123 | insertFile, 124 | // { loading: mutationLoading, error: mutationError }, 125 | ] = useMutation(INSERT_FILE); 126 | 127 | const handleSubmit = async () => { 128 | if (!fileData || !fileInput.current) { 129 | // console.log("No file selected"); 130 | return; 131 | } 132 | 133 | const uuid = uuidv4(); 134 | const extension = fileData.name.split(".").pop(); 135 | const file_path = `/public/${uuid}.${extension}`; 136 | 137 | await storage.put(file_path, fileData, null, (d: any) => { 138 | setUploadCompleted((d.loaded / d.total) * 100); 139 | }); 140 | 141 | setUploadState(""); 142 | fileInput.current.value = ""; 143 | 144 | const downloadable_url = `${BACKEND_ENDPOINT}/storage/o${file_path}`; 145 | await insertFile({ 146 | variables: { 147 | file: { 148 | file_path, 149 | downloadable_url, 150 | }, 151 | }, 152 | }); 153 | }; 154 | 155 | return ( 156 |
157 |
158 |
159 |
{ 161 | e.preventDefault(); 162 | handleSubmit(); 163 | }} 164 | > 165 |
166 | { 169 | if (!e.target.files?.length) return; 170 | setFileData(e.target.files[0]); 171 | }} 172 | ref={fileInput} 173 | /> 174 | 182 |
183 |
184 |
185 |
186 | 196 |
197 | 198 | {uploadState === "UPLOADING" && ( 199 |
200 | {uploadCompleted} % 201 | {/* */} 202 |
203 | )} 204 |
205 |
206 | 207 |
208 |
209 | ); 210 | } 211 | -------------------------------------------------------------------------------- /src/generated/globalTypes.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | // @generated 4 | // This file was automatically generated and should not be edited. 5 | 6 | //============================================================== 7 | // START Enums and Input Objects 8 | //============================================================== 9 | 10 | /** 11 | * unique or primary key constraints on table "auth.account_providers" 12 | */ 13 | export enum auth_account_providers_constraint { 14 | account_providers_account_id_auth_provider_key = "account_providers_account_id_auth_provider_key", 15 | account_providers_auth_provider_auth_provider_unique_id_key = "account_providers_auth_provider_auth_provider_unique_id_key", 16 | account_providers_pkey = "account_providers_pkey", 17 | } 18 | 19 | /** 20 | * update columns of table "auth.account_providers" 21 | */ 22 | export enum auth_account_providers_update_column { 23 | account_id = "account_id", 24 | auth_provider = "auth_provider", 25 | auth_provider_unique_id = "auth_provider_unique_id", 26 | created_at = "created_at", 27 | id = "id", 28 | updated_at = "updated_at", 29 | } 30 | 31 | /** 32 | * unique or primary key constraints on table "auth.account_roles" 33 | */ 34 | export enum auth_account_roles_constraint { 35 | account_roles_pkey = "account_roles_pkey", 36 | user_roles_account_id_role_key = "user_roles_account_id_role_key", 37 | } 38 | 39 | /** 40 | * update columns of table "auth.account_roles" 41 | */ 42 | export enum auth_account_roles_update_column { 43 | account_id = "account_id", 44 | created_at = "created_at", 45 | id = "id", 46 | role = "role", 47 | } 48 | 49 | /** 50 | * unique or primary key constraints on table "auth.accounts" 51 | */ 52 | export enum auth_accounts_constraint { 53 | accounts_email_key = "accounts_email_key", 54 | accounts_new_email_key = "accounts_new_email_key", 55 | accounts_pkey = "accounts_pkey", 56 | accounts_user_id_key = "accounts_user_id_key", 57 | } 58 | 59 | /** 60 | * update columns of table "auth.accounts" 61 | */ 62 | export enum auth_accounts_update_column { 63 | active = "active", 64 | created_at = "created_at", 65 | custom_register_data = "custom_register_data", 66 | default_role = "default_role", 67 | email = "email", 68 | id = "id", 69 | is_anonymous = "is_anonymous", 70 | mfa_enabled = "mfa_enabled", 71 | new_email = "new_email", 72 | otp_secret = "otp_secret", 73 | password_hash = "password_hash", 74 | ticket = "ticket", 75 | ticket_expires_at = "ticket_expires_at", 76 | updated_at = "updated_at", 77 | user_id = "user_id", 78 | } 79 | 80 | /** 81 | * unique or primary key constraints on table "auth.providers" 82 | */ 83 | export enum auth_providers_constraint { 84 | providers_pkey = "providers_pkey", 85 | } 86 | 87 | /** 88 | * update columns of table "auth.providers" 89 | */ 90 | export enum auth_providers_update_column { 91 | provider = "provider", 92 | } 93 | 94 | /** 95 | * unique or primary key constraints on table "auth.refresh_tokens" 96 | */ 97 | export enum auth_refresh_tokens_constraint { 98 | refresh_tokens_pkey = "refresh_tokens_pkey", 99 | } 100 | 101 | /** 102 | * update columns of table "auth.refresh_tokens" 103 | */ 104 | export enum auth_refresh_tokens_update_column { 105 | account_id = "account_id", 106 | created_at = "created_at", 107 | expires_at = "expires_at", 108 | refresh_token = "refresh_token", 109 | } 110 | 111 | /** 112 | * unique or primary key constraints on table "auth.roles" 113 | */ 114 | export enum auth_roles_constraint { 115 | roles_pkey = "roles_pkey", 116 | } 117 | 118 | /** 119 | * update columns of table "auth.roles" 120 | */ 121 | export enum auth_roles_update_column { 122 | role = "role", 123 | } 124 | 125 | /** 126 | * unique or primary key constraints on table "files" 127 | */ 128 | export enum files_constraint { 129 | files_pkey = "files_pkey", 130 | } 131 | 132 | /** 133 | * update columns of table "files" 134 | */ 135 | export enum files_update_column { 136 | created_at = "created_at", 137 | downloadable_url = "downloadable_url", 138 | file_path = "file_path", 139 | id = "id", 140 | user_id = "user_id", 141 | } 142 | 143 | /** 144 | * unique or primary key constraints on table "todos" 145 | */ 146 | export enum todos_constraint { 147 | todos_pkey = "todos_pkey", 148 | } 149 | 150 | /** 151 | * update columns of table "todos" 152 | */ 153 | export enum todos_update_column { 154 | created_at = "created_at", 155 | done = "done", 156 | id = "id", 157 | todo = "todo", 158 | updated_at = "updated_at", 159 | user_id = "user_id", 160 | } 161 | 162 | /** 163 | * unique or primary key constraints on table "users" 164 | */ 165 | export enum users_constraint { 166 | users_pkey = "users_pkey", 167 | } 168 | 169 | /** 170 | * update columns of table "users" 171 | */ 172 | export enum users_update_column { 173 | avatar_url = "avatar_url", 174 | created_at = "created_at", 175 | display_name = "display_name", 176 | id = "id", 177 | updated_at = "updated_at", 178 | } 179 | 180 | /** 181 | * expression to compare columns of type Boolean. All fields are combined with logical 'AND'. 182 | */ 183 | export interface Boolean_comparison_exp { 184 | _eq?: boolean | null; 185 | _gt?: boolean | null; 186 | _gte?: boolean | null; 187 | _in?: boolean[] | null; 188 | _is_null?: boolean | null; 189 | _lt?: boolean | null; 190 | _lte?: boolean | null; 191 | _neq?: boolean | null; 192 | _nin?: boolean[] | null; 193 | } 194 | 195 | /** 196 | * expression to compare columns of type String. All fields are combined with logical 'AND'. 197 | */ 198 | export interface String_comparison_exp { 199 | _eq?: string | null; 200 | _gt?: string | null; 201 | _gte?: string | null; 202 | _ilike?: string | null; 203 | _in?: string[] | null; 204 | _is_null?: boolean | null; 205 | _like?: string | null; 206 | _lt?: string | null; 207 | _lte?: string | null; 208 | _neq?: string | null; 209 | _nilike?: string | null; 210 | _nin?: string[] | null; 211 | _nlike?: string | null; 212 | _nsimilar?: string | null; 213 | _similar?: string | null; 214 | } 215 | 216 | /** 217 | * input type for inserting array relation for remote table "auth.account_providers" 218 | */ 219 | export interface auth_account_providers_arr_rel_insert_input { 220 | data: auth_account_providers_insert_input[]; 221 | on_conflict?: auth_account_providers_on_conflict | null; 222 | } 223 | 224 | /** 225 | * Boolean expression to filter rows from the table "auth.account_providers". All fields are combined with a logical 'AND'. 226 | */ 227 | export interface auth_account_providers_bool_exp { 228 | _and?: (auth_account_providers_bool_exp | null)[] | null; 229 | _not?: auth_account_providers_bool_exp | null; 230 | _or?: (auth_account_providers_bool_exp | null)[] | null; 231 | account?: auth_accounts_bool_exp | null; 232 | account_id?: uuid_comparison_exp | null; 233 | auth_provider?: String_comparison_exp | null; 234 | auth_provider_unique_id?: String_comparison_exp | null; 235 | created_at?: timestamptz_comparison_exp | null; 236 | id?: uuid_comparison_exp | null; 237 | provider?: auth_providers_bool_exp | null; 238 | updated_at?: timestamptz_comparison_exp | null; 239 | } 240 | 241 | /** 242 | * input type for inserting data into table "auth.account_providers" 243 | */ 244 | export interface auth_account_providers_insert_input { 245 | account?: auth_accounts_obj_rel_insert_input | null; 246 | account_id?: gql_uuid | null; 247 | auth_provider?: string | null; 248 | auth_provider_unique_id?: string | null; 249 | created_at?: gql_timestamptz | null; 250 | id?: gql_uuid | null; 251 | provider?: auth_providers_obj_rel_insert_input | null; 252 | updated_at?: gql_timestamptz | null; 253 | } 254 | 255 | /** 256 | * on conflict condition type for table "auth.account_providers" 257 | */ 258 | export interface auth_account_providers_on_conflict { 259 | constraint: auth_account_providers_constraint; 260 | update_columns: auth_account_providers_update_column[]; 261 | where?: auth_account_providers_bool_exp | null; 262 | } 263 | 264 | /** 265 | * input type for inserting array relation for remote table "auth.account_roles" 266 | */ 267 | export interface auth_account_roles_arr_rel_insert_input { 268 | data: auth_account_roles_insert_input[]; 269 | on_conflict?: auth_account_roles_on_conflict | null; 270 | } 271 | 272 | /** 273 | * Boolean expression to filter rows from the table "auth.account_roles". All fields are combined with a logical 'AND'. 274 | */ 275 | export interface auth_account_roles_bool_exp { 276 | _and?: (auth_account_roles_bool_exp | null)[] | null; 277 | _not?: auth_account_roles_bool_exp | null; 278 | _or?: (auth_account_roles_bool_exp | null)[] | null; 279 | account?: auth_accounts_bool_exp | null; 280 | account_id?: uuid_comparison_exp | null; 281 | created_at?: timestamptz_comparison_exp | null; 282 | id?: uuid_comparison_exp | null; 283 | role?: String_comparison_exp | null; 284 | roleByRole?: auth_roles_bool_exp | null; 285 | } 286 | 287 | /** 288 | * input type for inserting data into table "auth.account_roles" 289 | */ 290 | export interface auth_account_roles_insert_input { 291 | account?: auth_accounts_obj_rel_insert_input | null; 292 | account_id?: gql_uuid | null; 293 | created_at?: gql_timestamptz | null; 294 | id?: gql_uuid | null; 295 | role?: string | null; 296 | roleByRole?: auth_roles_obj_rel_insert_input | null; 297 | } 298 | 299 | /** 300 | * on conflict condition type for table "auth.account_roles" 301 | */ 302 | export interface auth_account_roles_on_conflict { 303 | constraint: auth_account_roles_constraint; 304 | update_columns: auth_account_roles_update_column[]; 305 | where?: auth_account_roles_bool_exp | null; 306 | } 307 | 308 | /** 309 | * input type for inserting array relation for remote table "auth.accounts" 310 | */ 311 | export interface auth_accounts_arr_rel_insert_input { 312 | data: auth_accounts_insert_input[]; 313 | on_conflict?: auth_accounts_on_conflict | null; 314 | } 315 | 316 | /** 317 | * Boolean expression to filter rows from the table "auth.accounts". All fields are combined with a logical 'AND'. 318 | */ 319 | export interface auth_accounts_bool_exp { 320 | _and?: (auth_accounts_bool_exp | null)[] | null; 321 | _not?: auth_accounts_bool_exp | null; 322 | _or?: (auth_accounts_bool_exp | null)[] | null; 323 | account_providers?: auth_account_providers_bool_exp | null; 324 | account_roles?: auth_account_roles_bool_exp | null; 325 | active?: Boolean_comparison_exp | null; 326 | created_at?: timestamptz_comparison_exp | null; 327 | custom_register_data?: jsonb_comparison_exp | null; 328 | default_role?: String_comparison_exp | null; 329 | email?: citext_comparison_exp | null; 330 | id?: uuid_comparison_exp | null; 331 | is_anonymous?: Boolean_comparison_exp | null; 332 | mfa_enabled?: Boolean_comparison_exp | null; 333 | new_email?: citext_comparison_exp | null; 334 | otp_secret?: String_comparison_exp | null; 335 | password_hash?: String_comparison_exp | null; 336 | refresh_tokens?: auth_refresh_tokens_bool_exp | null; 337 | role?: auth_roles_bool_exp | null; 338 | ticket?: uuid_comparison_exp | null; 339 | ticket_expires_at?: timestamptz_comparison_exp | null; 340 | updated_at?: timestamptz_comparison_exp | null; 341 | user?: users_bool_exp | null; 342 | user_id?: uuid_comparison_exp | null; 343 | } 344 | 345 | /** 346 | * input type for inserting data into table "auth.accounts" 347 | */ 348 | export interface auth_accounts_insert_input { 349 | account_providers?: auth_account_providers_arr_rel_insert_input | null; 350 | account_roles?: auth_account_roles_arr_rel_insert_input | null; 351 | active?: boolean | null; 352 | created_at?: gql_timestamptz | null; 353 | custom_register_data?: gql_jsonb | null; 354 | default_role?: string | null; 355 | email?: gql_citext | null; 356 | id?: gql_uuid | null; 357 | is_anonymous?: boolean | null; 358 | mfa_enabled?: boolean | null; 359 | new_email?: gql_citext | null; 360 | otp_secret?: string | null; 361 | password_hash?: string | null; 362 | refresh_tokens?: auth_refresh_tokens_arr_rel_insert_input | null; 363 | role?: auth_roles_obj_rel_insert_input | null; 364 | ticket?: gql_uuid | null; 365 | ticket_expires_at?: gql_timestamptz | null; 366 | updated_at?: gql_timestamptz | null; 367 | user?: users_obj_rel_insert_input | null; 368 | user_id?: gql_uuid | null; 369 | } 370 | 371 | /** 372 | * input type for inserting object relation for remote table "auth.accounts" 373 | */ 374 | export interface auth_accounts_obj_rel_insert_input { 375 | data: auth_accounts_insert_input; 376 | on_conflict?: auth_accounts_on_conflict | null; 377 | } 378 | 379 | /** 380 | * on conflict condition type for table "auth.accounts" 381 | */ 382 | export interface auth_accounts_on_conflict { 383 | constraint: auth_accounts_constraint; 384 | update_columns: auth_accounts_update_column[]; 385 | where?: auth_accounts_bool_exp | null; 386 | } 387 | 388 | /** 389 | * Boolean expression to filter rows from the table "auth.providers". All fields are combined with a logical 'AND'. 390 | */ 391 | export interface auth_providers_bool_exp { 392 | _and?: (auth_providers_bool_exp | null)[] | null; 393 | _not?: auth_providers_bool_exp | null; 394 | _or?: (auth_providers_bool_exp | null)[] | null; 395 | account_providers?: auth_account_providers_bool_exp | null; 396 | provider?: String_comparison_exp | null; 397 | } 398 | 399 | /** 400 | * input type for inserting data into table "auth.providers" 401 | */ 402 | export interface auth_providers_insert_input { 403 | account_providers?: auth_account_providers_arr_rel_insert_input | null; 404 | provider?: string | null; 405 | } 406 | 407 | /** 408 | * input type for inserting object relation for remote table "auth.providers" 409 | */ 410 | export interface auth_providers_obj_rel_insert_input { 411 | data: auth_providers_insert_input; 412 | on_conflict?: auth_providers_on_conflict | null; 413 | } 414 | 415 | /** 416 | * on conflict condition type for table "auth.providers" 417 | */ 418 | export interface auth_providers_on_conflict { 419 | constraint: auth_providers_constraint; 420 | update_columns: auth_providers_update_column[]; 421 | where?: auth_providers_bool_exp | null; 422 | } 423 | 424 | /** 425 | * input type for inserting array relation for remote table "auth.refresh_tokens" 426 | */ 427 | export interface auth_refresh_tokens_arr_rel_insert_input { 428 | data: auth_refresh_tokens_insert_input[]; 429 | on_conflict?: auth_refresh_tokens_on_conflict | null; 430 | } 431 | 432 | /** 433 | * Boolean expression to filter rows from the table "auth.refresh_tokens". All fields are combined with a logical 'AND'. 434 | */ 435 | export interface auth_refresh_tokens_bool_exp { 436 | _and?: (auth_refresh_tokens_bool_exp | null)[] | null; 437 | _not?: auth_refresh_tokens_bool_exp | null; 438 | _or?: (auth_refresh_tokens_bool_exp | null)[] | null; 439 | account?: auth_accounts_bool_exp | null; 440 | account_id?: uuid_comparison_exp | null; 441 | created_at?: timestamptz_comparison_exp | null; 442 | expires_at?: timestamptz_comparison_exp | null; 443 | refresh_token?: uuid_comparison_exp | null; 444 | } 445 | 446 | /** 447 | * input type for inserting data into table "auth.refresh_tokens" 448 | */ 449 | export interface auth_refresh_tokens_insert_input { 450 | account?: auth_accounts_obj_rel_insert_input | null; 451 | account_id?: gql_uuid | null; 452 | created_at?: gql_timestamptz | null; 453 | expires_at?: gql_timestamptz | null; 454 | refresh_token?: gql_uuid | null; 455 | } 456 | 457 | /** 458 | * on conflict condition type for table "auth.refresh_tokens" 459 | */ 460 | export interface auth_refresh_tokens_on_conflict { 461 | constraint: auth_refresh_tokens_constraint; 462 | update_columns: auth_refresh_tokens_update_column[]; 463 | where?: auth_refresh_tokens_bool_exp | null; 464 | } 465 | 466 | /** 467 | * Boolean expression to filter rows from the table "auth.roles". All fields are combined with a logical 'AND'. 468 | */ 469 | export interface auth_roles_bool_exp { 470 | _and?: (auth_roles_bool_exp | null)[] | null; 471 | _not?: auth_roles_bool_exp | null; 472 | _or?: (auth_roles_bool_exp | null)[] | null; 473 | account_roles?: auth_account_roles_bool_exp | null; 474 | accounts?: auth_accounts_bool_exp | null; 475 | role?: String_comparison_exp | null; 476 | } 477 | 478 | /** 479 | * input type for inserting data into table "auth.roles" 480 | */ 481 | export interface auth_roles_insert_input { 482 | account_roles?: auth_account_roles_arr_rel_insert_input | null; 483 | accounts?: auth_accounts_arr_rel_insert_input | null; 484 | role?: string | null; 485 | } 486 | 487 | /** 488 | * input type for inserting object relation for remote table "auth.roles" 489 | */ 490 | export interface auth_roles_obj_rel_insert_input { 491 | data: auth_roles_insert_input; 492 | on_conflict?: auth_roles_on_conflict | null; 493 | } 494 | 495 | /** 496 | * on conflict condition type for table "auth.roles" 497 | */ 498 | export interface auth_roles_on_conflict { 499 | constraint: auth_roles_constraint; 500 | update_columns: auth_roles_update_column[]; 501 | where?: auth_roles_bool_exp | null; 502 | } 503 | 504 | /** 505 | * expression to compare columns of type citext. All fields are combined with logical 'AND'. 506 | */ 507 | export interface citext_comparison_exp { 508 | _eq?: gql_citext | null; 509 | _gt?: gql_citext | null; 510 | _gte?: gql_citext | null; 511 | _ilike?: string | null; 512 | _in?: gql_citext[] | null; 513 | _is_null?: boolean | null; 514 | _like?: string | null; 515 | _lt?: gql_citext | null; 516 | _lte?: gql_citext | null; 517 | _neq?: gql_citext | null; 518 | _nilike?: string | null; 519 | _nin?: gql_citext[] | null; 520 | _nlike?: string | null; 521 | _nsimilar?: string | null; 522 | _similar?: string | null; 523 | } 524 | 525 | /** 526 | * input type for inserting array relation for remote table "files" 527 | */ 528 | export interface files_arr_rel_insert_input { 529 | data: files_insert_input[]; 530 | on_conflict?: files_on_conflict | null; 531 | } 532 | 533 | /** 534 | * Boolean expression to filter rows from the table "files". All fields are combined with a logical 'AND'. 535 | */ 536 | export interface files_bool_exp { 537 | _and?: (files_bool_exp | null)[] | null; 538 | _not?: files_bool_exp | null; 539 | _or?: (files_bool_exp | null)[] | null; 540 | created_at?: timestamptz_comparison_exp | null; 541 | downloadable_url?: String_comparison_exp | null; 542 | file_path?: String_comparison_exp | null; 543 | id?: uuid_comparison_exp | null; 544 | user?: users_bool_exp | null; 545 | user_id?: uuid_comparison_exp | null; 546 | } 547 | 548 | /** 549 | * input type for inserting data into table "files" 550 | */ 551 | export interface files_insert_input { 552 | created_at?: gql_timestamptz | null; 553 | downloadable_url?: string | null; 554 | file_path?: string | null; 555 | id?: gql_uuid | null; 556 | user?: users_obj_rel_insert_input | null; 557 | user_id?: gql_uuid | null; 558 | } 559 | 560 | /** 561 | * on conflict condition type for table "files" 562 | */ 563 | export interface files_on_conflict { 564 | constraint: files_constraint; 565 | update_columns: files_update_column[]; 566 | where?: files_bool_exp | null; 567 | } 568 | 569 | /** 570 | * expression to compare columns of type jsonb. All fields are combined with logical 'AND'. 571 | */ 572 | export interface jsonb_comparison_exp { 573 | _contained_in?: gql_jsonb | null; 574 | _contains?: gql_jsonb | null; 575 | _eq?: gql_jsonb | null; 576 | _gt?: gql_jsonb | null; 577 | _gte?: gql_jsonb | null; 578 | _has_key?: string | null; 579 | _has_keys_all?: string[] | null; 580 | _has_keys_any?: string[] | null; 581 | _in?: gql_jsonb[] | null; 582 | _is_null?: boolean | null; 583 | _lt?: gql_jsonb | null; 584 | _lte?: gql_jsonb | null; 585 | _neq?: gql_jsonb | null; 586 | _nin?: gql_jsonb[] | null; 587 | } 588 | 589 | /** 590 | * expression to compare columns of type timestamptz. All fields are combined with logical 'AND'. 591 | */ 592 | export interface timestamptz_comparison_exp { 593 | _eq?: gql_timestamptz | null; 594 | _gt?: gql_timestamptz | null; 595 | _gte?: gql_timestamptz | null; 596 | _in?: gql_timestamptz[] | null; 597 | _is_null?: boolean | null; 598 | _lt?: gql_timestamptz | null; 599 | _lte?: gql_timestamptz | null; 600 | _neq?: gql_timestamptz | null; 601 | _nin?: gql_timestamptz[] | null; 602 | } 603 | 604 | /** 605 | * input type for inserting array relation for remote table "todos" 606 | */ 607 | export interface todos_arr_rel_insert_input { 608 | data: todos_insert_input[]; 609 | on_conflict?: todos_on_conflict | null; 610 | } 611 | 612 | /** 613 | * Boolean expression to filter rows from the table "todos". All fields are combined with a logical 'AND'. 614 | */ 615 | export interface todos_bool_exp { 616 | _and?: (todos_bool_exp | null)[] | null; 617 | _not?: todos_bool_exp | null; 618 | _or?: (todos_bool_exp | null)[] | null; 619 | created_at?: timestamptz_comparison_exp | null; 620 | done?: Boolean_comparison_exp | null; 621 | id?: uuid_comparison_exp | null; 622 | todo?: String_comparison_exp | null; 623 | updated_at?: timestamptz_comparison_exp | null; 624 | user?: users_bool_exp | null; 625 | user_id?: uuid_comparison_exp | null; 626 | } 627 | 628 | /** 629 | * input type for inserting data into table "todos" 630 | */ 631 | export interface todos_insert_input { 632 | created_at?: gql_timestamptz | null; 633 | done?: boolean | null; 634 | id?: gql_uuid | null; 635 | todo?: string | null; 636 | updated_at?: gql_timestamptz | null; 637 | user?: users_obj_rel_insert_input | null; 638 | user_id?: gql_uuid | null; 639 | } 640 | 641 | /** 642 | * on conflict condition type for table "todos" 643 | */ 644 | export interface todos_on_conflict { 645 | constraint: todos_constraint; 646 | update_columns: todos_update_column[]; 647 | where?: todos_bool_exp | null; 648 | } 649 | 650 | /** 651 | * input type for updating data in table "todos" 652 | */ 653 | export interface todos_set_input { 654 | created_at?: gql_timestamptz | null; 655 | done?: boolean | null; 656 | id?: gql_uuid | null; 657 | todo?: string | null; 658 | updated_at?: gql_timestamptz | null; 659 | user_id?: gql_uuid | null; 660 | } 661 | 662 | /** 663 | * Boolean expression to filter rows from the table "users". All fields are combined with a logical 'AND'. 664 | */ 665 | export interface users_bool_exp { 666 | _and?: (users_bool_exp | null)[] | null; 667 | _not?: users_bool_exp | null; 668 | _or?: (users_bool_exp | null)[] | null; 669 | account?: auth_accounts_bool_exp | null; 670 | avatar_url?: String_comparison_exp | null; 671 | created_at?: timestamptz_comparison_exp | null; 672 | display_name?: String_comparison_exp | null; 673 | files?: files_bool_exp | null; 674 | id?: uuid_comparison_exp | null; 675 | todos?: todos_bool_exp | null; 676 | updated_at?: timestamptz_comparison_exp | null; 677 | } 678 | 679 | /** 680 | * input type for inserting data into table "users" 681 | */ 682 | export interface users_insert_input { 683 | account?: auth_accounts_obj_rel_insert_input | null; 684 | avatar_url?: string | null; 685 | created_at?: gql_timestamptz | null; 686 | display_name?: string | null; 687 | files?: files_arr_rel_insert_input | null; 688 | id?: gql_uuid | null; 689 | todos?: todos_arr_rel_insert_input | null; 690 | updated_at?: gql_timestamptz | null; 691 | } 692 | 693 | /** 694 | * input type for inserting object relation for remote table "users" 695 | */ 696 | export interface users_obj_rel_insert_input { 697 | data: users_insert_input; 698 | on_conflict?: users_on_conflict | null; 699 | } 700 | 701 | /** 702 | * on conflict condition type for table "users" 703 | */ 704 | export interface users_on_conflict { 705 | constraint: users_constraint; 706 | update_columns: users_update_column[]; 707 | where?: users_bool_exp | null; 708 | } 709 | 710 | /** 711 | * expression to compare columns of type uuid. All fields are combined with logical 'AND'. 712 | */ 713 | export interface uuid_comparison_exp { 714 | _eq?: gql_uuid | null; 715 | _gt?: gql_uuid | null; 716 | _gte?: gql_uuid | null; 717 | _in?: gql_uuid[] | null; 718 | _is_null?: boolean | null; 719 | _lt?: gql_uuid | null; 720 | _lte?: gql_uuid | null; 721 | _neq?: gql_uuid | null; 722 | _nin?: gql_uuid[] | null; 723 | } 724 | 725 | //============================================================== 726 | // END Enums and Input Objects 727 | //============================================================== 728 | --------------------------------------------------------------------------------