├── hasura ├── metadata │ ├── actions.graphql │ ├── allow_list.yaml │ ├── functions.yaml │ ├── cron_triggers.yaml │ ├── remote_schemas.yaml │ ├── version.yaml │ ├── query_collections.yaml │ ├── actions.yaml │ └── tables.yaml ├── migrations │ ├── 1623708795191_create_table_public_roles │ │ ├── down.sql │ │ └── up.sql │ ├── 1625684913027_create_table_public_list_items │ │ ├── down.sql │ │ └── up.sql │ ├── 1623708911712_insert_user_roles │ │ └── up.sql │ ├── 1623708958885_set_fk_public_users_role │ │ ├── down.sql │ │ └── up.sql │ ├── 1625352734443_modify_primarykey_public_users │ │ ├── up.sql │ │ └── down.sql │ ├── 1623250515702_alter_table_public_users_add_column_role │ │ ├── down.sql │ │ └── up.sql │ ├── 1625352807220_alter_table_public_users_drop_column_id │ │ ├── up.sql │ │ └── down.sql │ ├── 1625352832916_modify_primarykey_public_users │ │ ├── down.sql │ │ └── up.sql │ ├── 1623797034396_alter_table_public_users_add_column_oauth_id │ │ ├── down.sql │ │ └── up.sql │ ├── 1625356691357_alter_table_public_users_alter_column_id │ │ ├── down.sql │ │ └── up.sql │ ├── 1626100749047_alter_table_public_users_alter_column_id │ │ ├── up.sql │ │ └── down.sql │ ├── 1623855653231_alter_table_public_users_add_unique_email │ │ ├── down.sql │ │ └── up.sql │ ├── 1626101416460_alter_table_public_users_drop_column_oauth_id │ │ ├── up.sql │ │ └── down.sql │ ├── 1623709220354_alter_table_public_users_alter_column_role │ │ ├── down.sql │ │ └── up.sql │ ├── 1625352826984_alter_table_public_users_add_column_id │ │ ├── up.sql │ │ └── down.sql │ ├── 1623709115316_set_fk_public_users_role │ │ ├── up.sql │ │ └── down.sql │ └── 1623249075556_create_user_sessions │ │ └── up.sql ├── config.yaml ├── docker-compose.yml └── wait-for-url.js ├── .babelrc ├── .eslintignore ├── .storybook ├── .babelrc ├── preview-body.html └── main.js ├── src ├── utils │ ├── typedFetch │ │ ├── RESPONSE_TYPE.ts │ │ ├── HTTP_METHODS.ts │ │ ├── ApiConfig.ts │ │ └── typedFetch.ts │ ├── logger │ │ ├── index.ts │ │ ├── logger.ts │ │ └── headerFormatter.ts │ ├── isLocalhost.ts │ ├── getInitialNameAvatar.ts │ ├── markdown.ts │ ├── code_generator │ │ └── code_gen_replace.ts │ ├── GqlSdkHelper.ts │ ├── docs.ts │ └── middleware │ │ └── logMiddleware.ts ├── @types │ └── index.d.ts ├── styles │ ├── sweetalert.css │ └── tailwind.css ├── graphql │ └── gqls │ │ ├── users │ │ ├── users_by_pk.gql │ │ ├── delete_users_by_pk.gql │ │ ├── users_fragment.gql │ │ ├── users.gql │ │ └── insert_users_one.gql │ │ └── list_items │ │ ├── list_items_by_pk.gql │ │ ├── delete_list_items_by_pk.gql │ │ ├── list_items_fragment.gql │ │ ├── list_items.gql │ │ └── insert_list_items_one.gql ├── model │ ├── schemas │ │ ├── AuthValidationSchema.ts │ │ ├── Users_validation_schema.ts │ │ ├── MessagesValidationSchema.ts │ │ ├── FormExampleValidationSchema.ts │ │ └── List_items_validation_schema.ts │ ├── site │ │ ├── RoleList.ts │ │ ├── LinksList.ts │ │ └── ThemeList.ts │ └── api-models │ │ ├── users │ │ ├── Current_user_role_api_get.ts │ │ ├── Users_api_get.ts │ │ ├── Users_by_pk_api_get.ts │ │ ├── Delete_users_by_pk_api_delete.ts │ │ └── Insert_users_one_api_post.ts │ │ ├── code-generator │ │ ├── Generate_component_api_post.ts │ │ └── Generate_crud_api_post.ts │ │ ├── list_items │ │ ├── List_items_api_get.ts │ │ ├── List_items_by_pk_api_get.ts │ │ ├── Delete_list_items_by_pk_api_delete.ts │ │ └── Insert_list_items_one_api_post.ts │ │ └── typed-fetch-examples │ │ ├── Fetch_tester_api_get.ts │ │ └── Fetch_tester_api_post.ts ├── components │ ├── ValidationError │ │ ├── ValidationErrorType.ts │ │ ├── ValidationError.tsx │ │ ├── ValidationError.stories.tsx │ │ └── ValidationError.test.tsx │ ├── forms │ │ ├── FormBaseProps.ts │ │ ├── FormLabel.tsx │ │ ├── FormToggle │ │ │ ├── FormToggle.tsx │ │ │ └── FormToggle.stories.tsx │ │ ├── FormTextarea │ │ │ ├── FormTextarea.tsx │ │ │ └── FormTextarea.stories.tsx │ │ ├── FormTextbox │ │ │ ├── FormTextarea.tsx │ │ │ └── FormTextarea.stories.tsx │ │ ├── FormInput │ │ │ ├── FormInput.tsx │ │ │ └── FormInput.stories.tsx │ │ ├── FormSelect │ │ │ ├── FormSelect.tsx │ │ │ ├── FormSelect.test.tsx │ │ │ └── FormSelect.stories.tsx │ │ ├── FormInputColor │ │ │ ├── FormInputColor.stories.tsx │ │ │ ├── FormInputColor_Form.tsx │ │ │ └── FormInputColor.tsx │ │ ├── FormImage │ │ │ ├── FormImage.tsx │ │ │ ├── FormImage.test.tsx │ │ │ └── FormImage.stories.tsx │ │ └── FormLocalImage │ │ │ ├── FormLocalImage.stories.tsx │ │ │ ├── FormLocalImage.test.tsx │ │ │ └── FormLocalImage.tsx │ ├── Loading │ │ ├── Loading.tsx │ │ └── Loading.stories.tsx │ ├── Layout │ │ ├── Layout.test.tsx │ │ ├── __snapshots__ │ │ │ └── Layout.test.tsx.snap │ │ └── Layout.stories.tsx │ ├── Users_Form │ │ └── Users_Form.stories.tsx │ ├── CodeBlock │ │ ├── CodeBlock.tsx │ │ ├── CodeBlock.test.tsx │ │ └── CodeBlock.stories.tsx │ ├── showErrorAlert.tsx │ ├── List_items_Form │ │ └── List_items_Form.stories.tsx │ ├── Header.tsx │ ├── helpers │ │ └── ChangeThemeLayout.tsx │ ├── DropDown │ │ ├── DropDown.stories.tsx │ │ └── DropDown.tsx │ ├── Pagination │ │ ├── Pagination.stories.tsx │ │ ├── Pagination.test.tsx │ │ └── Pagination.tsx │ └── Cards │ │ └── Cards.tsx ├── pages │ ├── _app.tsx │ ├── api │ │ ├── typed-fetch-examples │ │ │ ├── fetch_tester_api_post.ts │ │ │ └── fetch_tester_api_get.ts │ │ ├── list_items │ │ │ ├── list_items_by_pk_api_get.ts │ │ │ ├── list_items_api_get.ts │ │ │ ├── delete_list_items_by_pk_api_delete.ts │ │ │ └── insert_list_items_one_api_post.ts │ │ ├── users │ │ │ ├── users_by_pk_api_get.ts │ │ │ ├── current_user_role_api_get.ts │ │ │ ├── users_api_get.ts │ │ │ ├── delete_users_by_pk_api_delete.ts │ │ │ └── insert_users_one_api_post.ts │ │ ├── code-generator │ │ │ └── generate_component_api_post.ts │ │ └── auth │ │ │ └── [...auth0].ts │ ├── typed-fetch-examples │ │ ├── index.tsx │ │ └── typedFetch-react-query.tsx │ ├── code-generator │ │ └── index.tsx │ ├── 404.tsx │ ├── _document.tsx │ └── docs │ │ ├── [slug].tsx │ │ └── index.tsx └── docs │ ├── introduction.md │ ├── getting-started.md │ └── prism.md ├── .prettierignore ├── postcss.config.js ├── public ├── favicon.ico └── vercel.svg ├── next-env.d.ts ├── __mocks__ └── svgrMock.js ├── .prettierrc ├── .vscode ├── extensions.json └── settings.json ├── graphql.config.yml ├── jest.config.js ├── codegen.yml ├── tailwind.config.js ├── sentry.server.config.js ├── .gitignore ├── tsconfig.json ├── .github └── workflows │ ├── chromatic.yml │ └── ci.yml ├── sentry.client.config.js ├── .env.local.example ├── test └── pages │ └── index.test.tsx ├── LICENSE.txt ├── LICENSE ├── next.config.js ├── .eslintrc.js ├── .env.production.sh.example ├── CODE_OF_CONDUCT.md └── README.md /hasura/metadata/actions.graphql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hasura/metadata/allow_list.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /hasura/metadata/functions.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules/* 2 | **/out/* 3 | **/.next/* 4 | -------------------------------------------------------------------------------- /.storybook/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["next/babel"], 3 | "plugins": [] 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/typedFetch/RESPONSE_TYPE.ts: -------------------------------------------------------------------------------- 1 | export type RESPONSE_TYPE = 'json' | 'text' 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | yarn.lock 4 | package-lock.json 5 | public 6 | -------------------------------------------------------------------------------- /hasura/migrations/1623708795191_create_table_public_roles/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE "public"."roles"; 2 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { tailwindcss: {}, autoprefixer: {} } 3 | } 4 | -------------------------------------------------------------------------------- /src/utils/typedFetch/HTTP_METHODS.ts: -------------------------------------------------------------------------------- 1 | export type HTTP_METHODS = 'get' | 'post' | 'put' | 'delete' 2 | -------------------------------------------------------------------------------- /hasura/migrations/1625684913027_create_table_public_list_items/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE "public"."list_items"; 2 | -------------------------------------------------------------------------------- /src/@types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const content: any 3 | export default content 4 | } 5 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nextjs-opinionated/nextjs-opinionated-hasura/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/utils/logger/index.ts: -------------------------------------------------------------------------------- 1 | export { logger } from './logger' 2 | export { headerFormatter } from './headerFormatter' 3 | -------------------------------------------------------------------------------- /hasura/migrations/1623708911712_insert_user_roles/up.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO roles (name) VALUES 2 | ('user'), 3 | ('admin'); 4 | -------------------------------------------------------------------------------- /src/styles/sweetalert.css: -------------------------------------------------------------------------------- 1 | .sweetalert-action { 2 | margin: 0 !important; 3 | padding: 1em 0 1.5em 0 !important; 4 | } 5 | -------------------------------------------------------------------------------- /hasura/migrations/1623708958885_set_fk_public_users_role/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" drop constraint "users_role_fkey"; 2 | -------------------------------------------------------------------------------- /hasura/migrations/1625352734443_modify_primarykey_public_users/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" drop constraint "users_pkey"; 2 | -------------------------------------------------------------------------------- /src/styles/tailwind.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | -------------------------------------------------------------------------------- /hasura/metadata/actions.yaml: -------------------------------------------------------------------------------- 1 | actions: [] 2 | custom_types: 3 | enums: [] 4 | input_objects: [] 5 | objects: [] 6 | scalars: [] 7 | -------------------------------------------------------------------------------- /hasura/migrations/1623250515702_alter_table_public_users_add_column_role/down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "public"."users" DROP COLUMN "role"; 2 | -------------------------------------------------------------------------------- /hasura/migrations/1625352807220_alter_table_public_users_drop_column_id/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" drop column "id" cascade; 2 | -------------------------------------------------------------------------------- /hasura/migrations/1625352832916_modify_primarykey_public_users/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" drop constraint "users_pkey"; 2 | -------------------------------------------------------------------------------- /hasura/migrations/1623250515702_alter_table_public_users_add_column_role/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "public"."users" ADD COLUMN "role" text NULL; 2 | -------------------------------------------------------------------------------- /hasura/migrations/1623797034396_alter_table_public_users_add_column_oauth_id/down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "public"."users" DROP COLUMN "oauth_id"; 2 | -------------------------------------------------------------------------------- /hasura/migrations/1625356691357_alter_table_public_users_alter_column_id/down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "public"."users" ALTER COLUMN "id" TYPE uuid; 2 | -------------------------------------------------------------------------------- /hasura/migrations/1625356691357_alter_table_public_users_alter_column_id/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "public"."users" ALTER COLUMN "id" TYPE text; 2 | -------------------------------------------------------------------------------- /hasura/migrations/1623708795191_create_table_public_roles/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "public"."roles"("name" text NOT NULL, PRIMARY KEY ("name") ); 2 | -------------------------------------------------------------------------------- /hasura/migrations/1626100749047_alter_table_public_users_alter_column_id/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "public"."users" ALTER COLUMN "id" drop default; 2 | -------------------------------------------------------------------------------- /src/graphql/gqls/users/users_by_pk.gql: -------------------------------------------------------------------------------- 1 | query users_by_pk($id: String!) { 2 | users_by_pk(id: $id) { 3 | ...users_fragment 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /hasura/migrations/1623855653231_alter_table_public_users_add_unique_email/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" drop constraint "users_email_key"; 2 | -------------------------------------------------------------------------------- /hasura/migrations/1626101416460_alter_table_public_users_drop_column_oauth_id/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" drop column "oauth_id" cascade; 2 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | -------------------------------------------------------------------------------- /hasura/migrations/1623709220354_alter_table_public_users_alter_column_role/down.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE ONLY "public"."users" ALTER COLUMN "role" DROP DEFAULT; 2 | -------------------------------------------------------------------------------- /hasura/migrations/1623797034396_alter_table_public_users_add_column_oauth_id/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "public"."users" ADD COLUMN "oauth_id" text NULL UNIQUE; 2 | -------------------------------------------------------------------------------- /src/graphql/gqls/users/delete_users_by_pk.gql: -------------------------------------------------------------------------------- 1 | mutation delete_users_by_pk($id: String!) { 2 | delete_users_by_pk(id: $id) { 3 | id 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/graphql/gqls/users/users_fragment.gql: -------------------------------------------------------------------------------- 1 | fragment users_fragment on users { 2 | id 3 | name 4 | email 5 | image 6 | role 7 | created_at 8 | } 9 | -------------------------------------------------------------------------------- /hasura/migrations/1623709220354_alter_table_public_users_alter_column_role/up.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE ONLY "public"."users" ALTER COLUMN "role" SET DEFAULT 'user'; 2 | -------------------------------------------------------------------------------- /hasura/migrations/1626100749047_alter_table_public_users_alter_column_id/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" alter column "id" set default gen_random_uuid(); 2 | -------------------------------------------------------------------------------- /src/utils/isLocalhost.ts: -------------------------------------------------------------------------------- 1 | export default function isLocalhost() { 2 | return typeof window !== 'undefined' && document.location.host.match(/localhost/) 3 | } 4 | -------------------------------------------------------------------------------- /.storybook/preview-body.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /hasura/migrations/1623855653231_alter_table_public_users_add_unique_email/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" add constraint "users_email_key" unique ("email"); 2 | -------------------------------------------------------------------------------- /hasura/migrations/1625352734443_modify_primarykey_public_users/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" 2 | add constraint "users_pkey" 3 | primary key ("id"); 4 | -------------------------------------------------------------------------------- /hasura/migrations/1625352832916_modify_primarykey_public_users/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" 2 | add constraint "users_pkey" 3 | primary key ("id"); 4 | -------------------------------------------------------------------------------- /src/graphql/gqls/list_items/list_items_by_pk.gql: -------------------------------------------------------------------------------- 1 | query list_items_by_pk($id: uuid!) { 2 | list_items_by_pk(id: $id) { 3 | ...list_items_fragment 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/graphql/gqls/list_items/delete_list_items_by_pk.gql: -------------------------------------------------------------------------------- 1 | mutation delete_list_items_by_pk($id: uuid!) { 2 | delete_list_items_by_pk(id: $id) { 3 | id 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/graphql/gqls/list_items/list_items_fragment.gql: -------------------------------------------------------------------------------- 1 | fragment list_items_fragment on list_items { 2 | id 3 | title 4 | body 5 | url 6 | imageUrl 7 | publishedAt 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/getInitialNameAvatar.ts: -------------------------------------------------------------------------------- 1 | export const getInitialNameAvatar = (name?: string | null) => 2 | `https://ui-avatars.com/api/?background=0D8ABC&color=fff&name=${name || ''}` 3 | -------------------------------------------------------------------------------- /src/model/schemas/AuthValidationSchema.ts: -------------------------------------------------------------------------------- 1 | import * as z from 'zod' 2 | 3 | export const AuthValidationSchema = z.object({ 4 | email: z.string().email({ message: 'invalid email' }), 5 | }) 6 | -------------------------------------------------------------------------------- /__mocks__/svgrMock.js: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | export default "SvgrURL" 3 | const SvgrMock = React.forwardRef((props, ref) => ) 4 | export const ReactComponent = SvgrMock 5 | -------------------------------------------------------------------------------- /src/utils/logger/logger.ts: -------------------------------------------------------------------------------- 1 | import pino from 'pino' 2 | 3 | // create pino loggger 4 | export const logger = pino({ 5 | level: 'debug', 6 | prettyPrint: { colorize: true, timestampKey: 'time' }, 7 | }) 8 | -------------------------------------------------------------------------------- /hasura/migrations/1625352826984_alter_table_public_users_add_column_id/up.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION IF NOT EXISTS pgcrypto; 2 | alter table "public"."users" add column "id" uuid 3 | not null default gen_random_uuid(); 4 | -------------------------------------------------------------------------------- /hasura/config.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | endpoint: http://localhost:8080 3 | admin_secret: admin_secret_local_zzz 4 | metadata_directory: metadata 5 | actions: 6 | kind: synchronous 7 | handler_webhook_baseurl: http://localhost:3000 8 | -------------------------------------------------------------------------------- /src/components/ValidationError/ValidationErrorType.ts: -------------------------------------------------------------------------------- 1 | export interface ValidationErrorType { 2 | code: string 3 | minimum?: number 4 | type?: string 5 | inclusive?: boolean 6 | message: string 7 | path: string[] 8 | } 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "semi": false, 5 | "jsxSingleQuote": true, 6 | "trailingComma": "es5", 7 | "endOfLine": "auto", 8 | "useTabs": false, 9 | "tabWidth": 2 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/typedFetch/ApiConfig.ts: -------------------------------------------------------------------------------- 1 | import { HTTP_METHODS } from './HTTP_METHODS' 2 | import { RESPONSE_TYPE } from './RESPONSE_TYPE' 3 | 4 | export type ApiConfig = { 5 | url: string 6 | method: HTTP_METHODS 7 | responseType: RESPONSE_TYPE 8 | } 9 | -------------------------------------------------------------------------------- /src/graphql/gqls/users/users.gql: -------------------------------------------------------------------------------- 1 | query users($limit: Int, $offset: Int) { 2 | users(limit: $limit, offset: $offset, order_by: { created_at: asc }) { 3 | ...users_fragment 4 | } 5 | users_aggregate { 6 | aggregate { 7 | count 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /hasura/migrations/1623708958885_set_fk_public_users_role/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" 2 | add constraint "users_role_fkey" 3 | foreign key ("role") 4 | references "public"."roles" 5 | ("name") on update cascade on delete set null; 6 | -------------------------------------------------------------------------------- /hasura/migrations/1625352807220_alter_table_public_users_drop_column_id/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" alter column "id" set default nextval('users_id_seq'::regclass); 2 | alter table "public"."users" alter column "id" drop not null; 3 | alter table "public"."users" add column "id" int4; 4 | -------------------------------------------------------------------------------- /hasura/migrations/1626101416460_alter_table_public_users_drop_column_oauth_id/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" add constraint "users_oauth_id_key" unique (oauth_id); 2 | alter table "public"."users" alter column "oauth_id" drop not null; 3 | alter table "public"."users" add column "oauth_id" text; 4 | -------------------------------------------------------------------------------- /src/graphql/gqls/list_items/list_items.gql: -------------------------------------------------------------------------------- 1 | query list_items($limit: Int, $offset: Int) { 2 | list_items(limit: $limit, offset: $offset, order_by: { created_at: asc }) { 3 | ...list_items_fragment 4 | } 5 | list_items_aggregate { 6 | aggregate { 7 | count 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/forms/FormBaseProps.ts: -------------------------------------------------------------------------------- 1 | export interface FormBaseProps { 2 | name: string 3 | label?: string 4 | labelDescription?: string 5 | placeholder?: string 6 | register: any 7 | defaultValue?: any 8 | validationErrors: any 9 | className?: string 10 | disabled?: boolean 11 | } 12 | -------------------------------------------------------------------------------- /hasura/migrations/1623709115316_set_fk_public_users_role/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" drop constraint "users_role_fkey", 2 | add constraint "users_role_fkey" 3 | foreign key ("role") 4 | references "public"."roles" 5 | ("name") on update cascade on delete set null; 6 | -------------------------------------------------------------------------------- /src/graphql/gqls/users/insert_users_one.gql: -------------------------------------------------------------------------------- 1 | mutation insert_users_one($object: users_insert_input!, $update_columns: [users_update_column!]!) { 2 | insert_users_one( 3 | object: $object 4 | on_conflict: { constraint: users_pkey, update_columns: $update_columns } 5 | ) { 6 | ...users_fragment 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "esbenp.prettier-vscode", 4 | "heybourn.headwind", 5 | "petermekhaeil.vscode-tailwindcss-explorer", 6 | "dbaeumer.vscode-eslint", 7 | "graphql.vscode-graphql", 8 | "ms-vsliveshare.vsliveshare", 9 | "wayou.vscode-todo-highlight" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /hasura/migrations/1623709115316_set_fk_public_users_role/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" drop constraint "users_role_fkey", 2 | add constraint "users_role_fkey" 3 | foreign key ("role") 4 | references "public"."roles" 5 | ("name") 6 | on update cascade 7 | on delete set null; 8 | -------------------------------------------------------------------------------- /hasura/migrations/1625352826984_alter_table_public_users_add_column_id/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."users" drop column "id" cascade 2 | alter table "public"."users" drop column "id"; 3 | -- Could not auto-generate a down migration. 4 | -- Please write an appropriate down migration for the SQL below: 5 | -- CREATE EXTENSION IF NOT EXISTS pgcrypto; 6 | -------------------------------------------------------------------------------- /src/utils/logger/headerFormatter.ts: -------------------------------------------------------------------------------- 1 | import { IncomingHttpHeaders } from 'http' 2 | 3 | export const headerFormatter = (headers: IncomingHttpHeaders) => { 4 | const keyValues = {} 5 | Object.keys(headers).forEach((key) => { 6 | const newKey = key.replace(/-/g, '_') 7 | keyValues[newKey] = headers[key] 8 | }) 9 | 10 | return keyValues 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/markdown.ts: -------------------------------------------------------------------------------- 1 | import remark from 'remark' 2 | import html from 'remark-html' 3 | import prism from 'remark-prism' 4 | import { VFileCompatible } from 'vfile' 5 | 6 | export const markdownToHtml = async (markdown: VFileCompatible) => { 7 | const result = await remark().use(html).use(prism).process(markdown) 8 | return result.toString() 9 | } 10 | -------------------------------------------------------------------------------- /graphql.config.yml: -------------------------------------------------------------------------------- 1 | schema: 2 | - http://localhost:8080/v1/graphql: 3 | headers: 4 | x-hasura-admin-secret: admin_secret_local_zzz 5 | documents: 'src/graphql/gqls/**/*.gql' 6 | extensions: 7 | endpoints: 8 | default: 9 | url: http://localhost:8080/v1/graphql 10 | headers: 11 | x-hasura-admin-secret: admin_secret_local_zzz 12 | -------------------------------------------------------------------------------- /src/graphql/gqls/list_items/insert_list_items_one.gql: -------------------------------------------------------------------------------- 1 | mutation insert_list_items_one( 2 | $object: list_items_insert_input! 3 | $update_columns: [list_items_update_column!]! 4 | ) { 5 | insert_list_items_one( 6 | object: $object 7 | on_conflict: { constraint: list_items_pkey, update_columns: $update_columns } 8 | ) { 9 | ...list_items_fragment 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/model/schemas/Users_validation_schema.ts: -------------------------------------------------------------------------------- 1 | // import _ from 'lodash' 2 | import * as z from 'zod' 3 | import { Roles_Enum } from '../../graphql/generated' 4 | 5 | export const Users_validation_schema = z.object({ 6 | id: z.string().nonempty(), 7 | name: z.string().nonempty(), 8 | email: z.string().email(), 9 | role: z.nativeEnum(Roles_Enum), 10 | image: z.string().nullish(), 11 | }) 12 | -------------------------------------------------------------------------------- /src/model/site/RoleList.ts: -------------------------------------------------------------------------------- 1 | export enum Roles_Enum { 2 | Admin = 'admin', 3 | User = 'user', 4 | } 5 | 6 | export type RoleProps = { 7 | id: Roles_Enum 8 | name: string 9 | } 10 | 11 | export const RoleList: { 12 | [id in Roles_Enum]: RoleProps 13 | } = { 14 | user: { 15 | id: Roles_Enum.User, 16 | name: 'User', 17 | }, 18 | admin: { 19 | id: Roles_Enum.Admin, 20 | name: 'Administrator', 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const TEST_REGEX = '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|js?|tsx?|ts?)$' 2 | 3 | module.exports = { 4 | testRegex: TEST_REGEX, 5 | transform: { 6 | '^.+\\.tsx?$': 'babel-jest', 7 | }, 8 | testPathIgnorePatterns: ['/build/', '/node_modules/', '/dist/'], 9 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], 10 | moduleNameMapper: { 11 | '^~(.*)$': '/src/$1', 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /src/model/api-models/users/Current_user_role_api_get.ts: -------------------------------------------------------------------------------- 1 | import { Roles_Enum } from '../../../graphql/generated' 2 | import { ApiConfig } from '../../../utils/typedFetch/ApiConfig' 3 | 4 | export interface Current_user_role_api_get { 5 | input: null 6 | output: Roles_Enum 7 | } 8 | 9 | export const current_user_role_api_get_Config: ApiConfig = { 10 | url: '/api/users/current_user_role_api_get', 11 | method: 'get', 12 | responseType: 'json', 13 | } 14 | -------------------------------------------------------------------------------- /src/model/api-models/users/Users_api_get.ts: -------------------------------------------------------------------------------- 1 | import { UsersQuery } from '../../../graphql/generated' 2 | import { ApiConfig } from '../../../utils/typedFetch/ApiConfig' 3 | 4 | export interface Users_api_get { 5 | input: { 6 | limit: string 7 | current_page: string 8 | } 9 | output: UsersQuery 10 | } 11 | 12 | export const users_api_get_Config: ApiConfig = { 13 | url: '/api/users/users_api_get', 14 | method: 'get', 15 | responseType: 'json', 16 | } 17 | -------------------------------------------------------------------------------- /src/model/api-models/code-generator/Generate_component_api_post.ts: -------------------------------------------------------------------------------- 1 | import { ApiConfig } from '../../../utils/typedFetch/ApiConfig' 2 | 3 | export interface Generate_component_api_post { 4 | input: { 5 | name: string 6 | } 7 | output: { 8 | saved: boolean 9 | } 10 | } 11 | 12 | export const generate_component_api_post_Config: ApiConfig = { 13 | url: '/api/code-generator/generate_component_api_post', 14 | method: 'post', 15 | responseType: 'json', 16 | } 17 | -------------------------------------------------------------------------------- /src/model/api-models/users/Users_by_pk_api_get.ts: -------------------------------------------------------------------------------- 1 | import { Users_By_PkQuery } from '../../../graphql/generated' 2 | import { ApiConfig } from '../../../utils/typedFetch/ApiConfig' 3 | 4 | export interface Users_by_pk_api_get { 5 | input: { 6 | id: string | string[] 7 | } 8 | output: Users_By_PkQuery 9 | } 10 | 11 | export const users_by_pk_api_get_Config: ApiConfig = { 12 | url: '/api/users/users_by_pk_api_get', 13 | method: 'get', 14 | responseType: 'json', 15 | } 16 | -------------------------------------------------------------------------------- /hasura/migrations/1623249075556_create_user_sessions/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users 2 | ( 3 | id TEXT, 4 | name VARCHAR(255), 5 | email VARCHAR(255), 6 | email_verified TIMESTAMPTZ, 7 | image TEXT, 8 | created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, 9 | updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, 10 | PRIMARY KEY (id) 11 | ); 12 | 13 | CREATE UNIQUE INDEX email 14 | ON users(email); 15 | -------------------------------------------------------------------------------- /codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: 3 | - http://localhost:8080/v1/graphql: 4 | headers: 5 | x-hasura-admin-secret: admin_secret_local_zzz 6 | documents: 'src/graphql/gqls/**/*.gql' 7 | generates: 8 | src/graphql/generated.ts: 9 | plugins: 10 | - add: 11 | content: 12 | - '/* eslint-disable no-shadow */' 13 | - '/* generated code */' 14 | - typescript 15 | - typescript-operations 16 | - typescript-graphql-request 17 | -------------------------------------------------------------------------------- /src/components/Loading/Loading.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { FaSpinner } from 'react-icons/fa' 3 | 4 | function Loading({ title = 'Loading', className = '' }) { 5 | return ( 6 |
7 |
8 | 9 |
10 |
11 | ) 12 | } 13 | 14 | export default Loading 15 | -------------------------------------------------------------------------------- /src/model/api-models/list_items/List_items_api_get.ts: -------------------------------------------------------------------------------- 1 | import { List_ItemsQuery } from '../../../graphql/generated' 2 | import { ApiConfig } from '../../../utils/typedFetch/ApiConfig' 3 | 4 | export interface List_Items_api_get { 5 | input: { 6 | limit: string 7 | current_page: string 8 | } 9 | output: List_ItemsQuery 10 | } 11 | 12 | export const list_items_api_get_Config: ApiConfig = { 13 | url: '/api/list_items/list_items_api_get', 14 | method: 'get', 15 | responseType: 'json', 16 | } 17 | -------------------------------------------------------------------------------- /src/model/api-models/users/Delete_users_by_pk_api_delete.ts: -------------------------------------------------------------------------------- 1 | import { Delete_Users_By_PkMutation } from '../../../graphql/generated' 2 | import { ApiConfig } from '../../../utils/typedFetch/ApiConfig' 3 | 4 | export interface Delete_users_by_pk_api_delete { 5 | input: { id: string } 6 | output: Delete_Users_By_PkMutation 7 | } 8 | 9 | export const delete_users_by_pk_api_delete_Config: ApiConfig = { 10 | url: '/api/users/delete_users_by_pk_api_delete', 11 | method: 'delete', 12 | responseType: 'json', 13 | } 14 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: { 3 | enabled: process.env.NODE_ENV === 'production', 4 | content: ['./src/**/*.html', './src/**/*.tsx', './src/**/*.jsx'], 5 | }, 6 | darkMode: false, // or 'media' or 'class' 7 | mode: 'jit', 8 | theme: { 9 | extend: {}, 10 | borderWidth: { 11 | 20: '20px', 12 | }, 13 | }, 14 | variants: { 15 | extend: {}, 16 | }, 17 | plugins: [require('@tailwindcss/typography'), require('daisyui'), require('@tailwindcss/forms')], 18 | } 19 | -------------------------------------------------------------------------------- /src/model/api-models/list_items/List_items_by_pk_api_get.ts: -------------------------------------------------------------------------------- 1 | import { List_Items_By_PkQuery } from '../../../graphql/generated' 2 | import { ApiConfig } from '../../../utils/typedFetch/ApiConfig' 3 | 4 | export interface List_items_by_pk_api_get { 5 | input: { 6 | id: string | string[] 7 | } 8 | output: List_Items_By_PkQuery 9 | } 10 | 11 | export const list_items_by_pk_api_get_Config: ApiConfig = { 12 | url: '/api/list_items/list_items_by_pk_api_get', 13 | method: 'get', 14 | responseType: 'json', 15 | } 16 | -------------------------------------------------------------------------------- /hasura/metadata/tables.yaml: -------------------------------------------------------------------------------- 1 | - table: 2 | schema: public 3 | name: list_items 4 | - table: 5 | schema: public 6 | name: roles 7 | is_enum: true 8 | array_relationships: 9 | - name: users 10 | using: 11 | foreign_key_constraint_on: 12 | column: role 13 | table: 14 | schema: public 15 | name: users 16 | - table: 17 | schema: public 18 | name: users 19 | object_relationships: 20 | - name: roleByRole 21 | using: 22 | foreign_key_constraint_on: role 23 | -------------------------------------------------------------------------------- /src/model/api-models/code-generator/Generate_crud_api_post.ts: -------------------------------------------------------------------------------- 1 | import { ApiConfig } from '../../../utils/typedFetch/ApiConfig' 2 | 3 | export interface Generate_crud_api_post { 4 | input: { 5 | table_name: string 6 | table_id: string 7 | } 8 | output: { 9 | saved: boolean 10 | vars: { 11 | [key: string]: string 12 | } 13 | } 14 | } 15 | 16 | export const generate_crud_api_post_Config: ApiConfig = { 17 | url: '/api/code-generator/generate_crud_api_post', 18 | method: 'post', 19 | responseType: 'json', 20 | } 21 | -------------------------------------------------------------------------------- /src/model/api-models/list_items/Delete_list_items_by_pk_api_delete.ts: -------------------------------------------------------------------------------- 1 | import { Delete_List_Items_By_PkMutation } from '../../../graphql/generated' 2 | import { ApiConfig } from '../../../utils/typedFetch/ApiConfig' 3 | 4 | export interface Delete_list_items_by_pk_api_delete { 5 | input: { id: string } 6 | output: Delete_List_Items_By_PkMutation 7 | } 8 | 9 | export const delete_list_items_by_pk_api_delete_Config: ApiConfig = { 10 | url: '/api/list_items/delete_list_items_by_pk_api_delete', 11 | method: 'delete', 12 | responseType: 'json', 13 | } 14 | -------------------------------------------------------------------------------- /src/model/api-models/typed-fetch-examples/Fetch_tester_api_get.ts: -------------------------------------------------------------------------------- 1 | import { ApiConfig } from '../../../utils/typedFetch/ApiConfig' 2 | 3 | export interface Fetch_tester_api_get { 4 | input: { 5 | some_string: string 6 | divide_by: string 7 | force_error: string 8 | } 9 | output: { 10 | message: string 11 | division_result: number 12 | } 13 | } 14 | 15 | export const fetch_tester_api_get_Config: ApiConfig = { 16 | url: '/api/typed-fetch-examples/fetch_tester_api_get', 17 | method: 'get', 18 | responseType: 'json', 19 | } 20 | -------------------------------------------------------------------------------- /src/model/api-models/typed-fetch-examples/Fetch_tester_api_post.ts: -------------------------------------------------------------------------------- 1 | import { ApiConfig } from '../../../utils/typedFetch/ApiConfig' 2 | 3 | export interface Fetch_tester_api_post { 4 | input: { 5 | some_string: string 6 | divide_by: number 7 | force_error: boolean 8 | } 9 | output: { 10 | message: string 11 | division_result: number 12 | } 13 | } 14 | 15 | export const fetch_tester_api_post_Config: ApiConfig = { 16 | url: '/api/typed-fetch-examples/fetch_tester_api_post', 17 | method: 'post', 18 | responseType: 'json', 19 | } 20 | -------------------------------------------------------------------------------- /src/model/api-models/list_items/Insert_list_items_one_api_post.ts: -------------------------------------------------------------------------------- 1 | import { Insert_List_Items_OneMutation, List_Items_Insert_Input } from '../../../graphql/generated' 2 | import { ApiConfig } from '../../../utils/typedFetch/ApiConfig' 3 | 4 | export interface Insert_list_items_one_api_post { 5 | input: List_Items_Insert_Input 6 | output: Insert_List_Items_OneMutation 7 | } 8 | 9 | export const insert_list_items_one_api_post_Config: ApiConfig = { 10 | url: '/api/list_items/insert_list_items_one_api_post', 11 | method: 'post', 12 | responseType: 'json', 13 | } 14 | -------------------------------------------------------------------------------- /src/components/Layout/Layout.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import renderer from 'react-test-renderer' 3 | import { Layout } from './Layout' 4 | import { UserProvider } from '@auth0/nextjs-auth0' 5 | 6 | test('renders correctly', () => { 7 | jest.mock('@auth0/nextjs-auth0', () => ({ 8 | useUser: () => ({ 9 | user: {}, 10 | }), 11 | })) 12 | const tree = renderer 13 | .create( 14 | 15 | Some Text 16 | 17 | ) 18 | .toJSON() 19 | expect(tree).toMatchSnapshot() 20 | }) 21 | -------------------------------------------------------------------------------- /src/model/api-models/users/Insert_users_one_api_post.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod' 2 | import { Insert_Users_OneMutation } from '../../../graphql/generated' 3 | import { ApiConfig } from '../../../utils/typedFetch/ApiConfig' 4 | import { Users_validation_schema } from '../../schemas/Users_validation_schema' 5 | 6 | export interface Insert_users_one_api_post { 7 | input: z.infer 8 | output: Insert_Users_OneMutation 9 | } 10 | 11 | export const insert_users_one_api_post_Config: ApiConfig = { 12 | url: '/api/users/insert_users_one_api_post', 13 | method: 'post', 14 | responseType: 'json', 15 | } 16 | -------------------------------------------------------------------------------- /sentry.server.config.js: -------------------------------------------------------------------------------- 1 | // This file configures the initialization of Sentry on the server. 2 | // The config you add here will be used whenever the server handles a request. 3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/ 4 | 5 | import * as Sentry from '@sentry/nextjs' 6 | 7 | const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN 8 | 9 | Sentry.init({ 10 | dsn: SENTRY_DSN, 11 | // Note: if you want to override the automatic release value, do not set a 12 | // `release` value here - use the environment variable `SENTRY_RELEASE`, so 13 | // that it will also get attached to your source maps 14 | }) 15 | -------------------------------------------------------------------------------- /src/model/site/LinksList.ts: -------------------------------------------------------------------------------- 1 | export type LinksListIds = 'home' | 'docs' | 'github' 2 | 3 | export type LinkProps = { 4 | id: LinksListIds 5 | name: string 6 | internalURL?: string 7 | externalURL?: string 8 | } 9 | 10 | export const LinksList: { 11 | [id in LinksListIds]: LinkProps 12 | } = { 13 | home: { 14 | id: 'home', 15 | name: 'Home', 16 | internalURL: '/', 17 | }, 18 | docs: { 19 | id: 'docs', 20 | name: 'Docs', 21 | internalURL: '/docs', 22 | }, 23 | github: { 24 | id: 'github', 25 | name: 'Github', 26 | externalURL: 'https://github.com/nextjs-opinionated/nextjs-opinionated-hasura', 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /.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 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | .vscode/settings.json 37 | .env.production.sh 38 | codegen.remote.yml 39 | src/tailwind.output.css 40 | .env.production.sh 41 | *.log -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "strictNullChecks": true, // https://github.com/colinhacks/zod/issues/408#issuecomment-826220442 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "esModuleInterop": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "jsx": "preserve" 17 | }, 18 | "include": ["src/**/*.ts", "src/**/*.tsx"], 19 | "exclude": ["**/node_modules", "hasura"] 20 | } 21 | -------------------------------------------------------------------------------- /src/utils/code_generator/code_gen_replace.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs-extra' 2 | 3 | export async function code_gen_replace({ 4 | from_file, 5 | to_file, 6 | replaces, 7 | }: { 8 | from_file: string 9 | to_file: string 10 | replaces: { 11 | from: string 12 | to: string 13 | }[] 14 | }) { 15 | await fs.ensureFile(to_file) 16 | 17 | // load 18 | let content_from = await fs.readFile(from_file, { 19 | encoding: 'utf8', 20 | }) 21 | 22 | // replace all 23 | replaces.forEach((replaceObj) => { 24 | content_from = content_from.replace(new RegExp(replaceObj.from, 'g'), replaceObj.to) 25 | }) 26 | 27 | // save 28 | await fs.writeFile(to_file, content_from, { 29 | encoding: 'utf8', 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /src/model/schemas/MessagesValidationSchema.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import * as z from 'zod' 3 | 4 | export const MessagesValidationSchema = z.object({ 5 | id: z.any().refine( 6 | (value) => { 7 | // optional if is on insert mode 8 | if (!value) { 9 | return true 10 | } 11 | return _.isInteger(value) 12 | }, 13 | { 14 | message: 'id must be a valid integer', 15 | } 16 | ), 17 | title: z.string().nonempty(), 18 | body: z.string().nullish(), 19 | url: z.string().nonempty(), 20 | imageUrl: z.string().nullish(), 21 | publishedAt: z.string().nullish(), 22 | publishedAt_date: z.string().nullish(), 23 | publishedAt_time: z.string().nullish(), 24 | }) 25 | -------------------------------------------------------------------------------- /.github/workflows/chromatic.yml: -------------------------------------------------------------------------------- 1 | # Workflow name 2 | name: 'Chromatic Deployment' 3 | 4 | # Event for the workflow 5 | on: push 6 | 7 | # List of jobs 8 | jobs: 9 | test: 10 | # Operating System 11 | runs-on: ubuntu-latest 12 | # Job steps 13 | steps: 14 | - uses: actions/checkout@v1 15 | - run: yarn 16 | #👇 Adds Chromatic as a step in the workflow 17 | - uses: chromaui/action@v1 18 | # Options required for Chromatic's GitHub Action 19 | with: 20 | #👇 Chromatic projectToken, see https://storybook.js.org/tutorials/intro-to-storybook/react/en/deploy/ to obtain it 21 | projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} 22 | token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /src/components/forms/FormLabel.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames' 2 | import React from 'react' 3 | import { FormBaseProps } from './FormBaseProps' 4 | 5 | export const FormLabel: React.FC> = ({ 6 | label, 7 | name, 8 | labelDescription, 9 | }) => { 10 | return ( 11 | <> 12 | 20 | {labelDescription && ( 21 | {labelDescription} 22 | )} 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /src/components/ValidationError/ValidationError.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ValidationErrorType } from './ValidationErrorType' 3 | 4 | export interface ValidationErrorProps { 5 | content: ValidationErrorType[] 6 | className?: string 7 | } 8 | 9 | export const ValidationError: React.FC = ({ 10 | content = [], 11 | className = '', 12 | }) => { 13 | return ( 14 |
15 | {content.map((error, index) => { 16 | return ( 17 |

{`${error?.path} : ${error?.message}`}

21 | ) 22 | })} 23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /sentry.client.config.js: -------------------------------------------------------------------------------- 1 | // This file configures the initialization of Sentry on the browser. 2 | // The config you add here will be used whenever a page is visited. 3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/ 4 | 5 | import * as Sentry from '@sentry/nextjs' 6 | import { Integrations } from '@sentry/tracing' 7 | 8 | const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN 9 | 10 | Sentry.init({ 11 | dsn: SENTRY_DSN, 12 | // Note: if you want to override the automatic release value, do not set a 13 | // `release` value here - use the environment variable `SENTRY_RELEASE`, so 14 | // that it will also get attached to your source maps 15 | integrations: [new Integrations.BrowserTracing()], 16 | tracesSampleRate: 1.0, 17 | }) 18 | -------------------------------------------------------------------------------- /src/components/Users_Form/Users_Form.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Story, Meta } from '@storybook/react/types-6-0' 3 | import { Users_Form, Users_FormProps } from './Users_Form' 4 | 5 | export default { 6 | title: 'Component/Pages/Users_Form', 7 | component: Users_Form, 8 | } as Meta 9 | 10 | const Template: Story = (args) => 11 | 12 | export const Users_Form_Empty = Template.bind({}) 13 | Users_Form_Empty.args = {} 14 | 15 | export const Users_Form_Filled = Template.bind({}) 16 | Users_Form_Filled.args = { 17 | initialFormData: { 18 | name: 'User Name', 19 | email: 'user@email.com', 20 | image: 'https://altphotos.com/photo/lovely-brunette-covers-her-mouth-behind-the-leaf-1882/', 21 | role: 'user', 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /src/components/forms/FormToggle/FormToggle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { FormBaseProps } from '../FormBaseProps' 3 | 4 | export const FormToggle: React.FC = ({ 5 | label, 6 | name, 7 | disabled, 8 | register, 9 | defaultValue, 10 | }) => { 11 | return ( 12 |
13 | 26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /.env.local.example: -------------------------------------------------------------------------------- 1 | # Site infos 2 | NEXT_PUBLIC_GA_ID="xxx" 3 | NEXT_PUBLIC_SITE_NAME="xxx" 4 | NEXT_PUBLIC_SITE_URL="xxx" 5 | NEXT_PUBLIC_SITE_IMAGE="xxx" 6 | NEXT_PUBLIC_SITE_DESCRIPTION="xxx" 7 | NEXT_PUBLIC_SITE_KEYWORDS="xxx" 8 | 9 | # Auth0 10 | AUTH0_SECRET="xxx" 11 | AUTH0_BASE_URL="xxx" 12 | AUTH0_ISSUER_BASE_URL="xxx" 13 | AUTH0_CLIENT_ID="xxx" 14 | AUTH0_CLIENT_SECRET="xxx" 15 | AUTH0_SCOPE="xxx" 16 | AUTH0_AUDIENCE="xxx" 17 | 18 | # Sentry 19 | NEXT_PUBLIC_SENTRY_DSN="xxx" 20 | SENTRY_DSN="xxx" 21 | SENTRY_SERVER_INIT_PATH="xxx" 22 | SENTRY_AUTH_TOKEN="xxx" 23 | SENTRY_URL="xxx" 24 | SENTRY_ORG="xxx" 25 | SENTRY_PROJECT="xxx" 26 | 27 | # Hasura 28 | HASURA_ADMIN_SECRET="xxxx" 29 | HASURA_PROD_SERVER_URL="xxxx" 30 | HASURA_PROD_ADMIN_SECRET="xxxx" 31 | NEXT_PUBLIC_HASURA_GRAPHQL_ENDPOINT="xxxx" 32 | NEXT_PUBLIC_HASURA_GRAPHQL_ENDPOINT_SUBSCRIPTION="xxxx" -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: [pull_request] 3 | 4 | jobs: 5 | build: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout Repository 9 | uses: actions/checkout@v2 10 | 11 | - name: Setup Node 12 | uses: actions/setup-node@v2 13 | with: 14 | node-version: 14.x 15 | 16 | - uses: actions/cache@v2 17 | id: yarn-cache 18 | with: 19 | path: | 20 | ~/cache 21 | !~/cache/exclude 22 | **/node_modules 23 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 24 | restore-keys: | 25 | ${{ runner.os }}-yarn- 26 | - name: Install dependencies 27 | run: yarn install 28 | 29 | - name: Test 30 | run: yarn test:ci 31 | 32 | - name: Build 33 | run: yarn build 34 | -------------------------------------------------------------------------------- /src/utils/GqlSdkHelper.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLClient } from 'graphql-request' 2 | import { getSdk } from '../graphql/generated' 3 | 4 | if (typeof window !== 'undefined') { 5 | // prevent this to be called from client browser 6 | throw new Error('WARNING: Only server calls are allowed!') 7 | } 8 | 9 | export default class GqlSdkHelper { 10 | private client: GraphQLClient 11 | 12 | private sdk: ReturnType 13 | 14 | constructor() { 15 | const gqlEndpoint = process.env.NEXT_PUBLIC_HASURA_GRAPHQL_ENDPOINT as string 16 | const adminSecret = process.env.HASURA_ADMIN_SECRET as string 17 | 18 | this.client = new GraphQLClient(gqlEndpoint, { 19 | headers: { 20 | 'x-hasura-admin-secret': adminSecret, 21 | }, 22 | }) 23 | this.sdk = getSdk(this.client) 24 | } 25 | 26 | public getSdk() { 27 | return this.sdk 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/CodeBlock/CodeBlock.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames' 2 | import React from 'react' 3 | 4 | export interface CodeBlockProps { 5 | content: any 6 | className?: string 7 | textType?: string 8 | dataPrefix?: string 9 | } 10 | 11 | export const CodeBlock: React.FC = ({ 12 | content, 13 | className = '', 14 | dataPrefix = '', 15 | textType = '', 16 | }) => { 17 | return ( 18 |
19 | {JSON.stringify(content, null, 2) 20 | .split('\n') 21 | .map((line, i) => ( 22 |
27 |             {line}
28 |           
29 | ))} 30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /src/components/Loading/Loading.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Meta } from '@storybook/react/types-6-0' 3 | import Loading from './Loading' 4 | 5 | export default { 6 | title: 'Component/Loading', 7 | component: Loading, 8 | decorators: [ 9 | (Story) => ( 10 | <> 11 | 12 | 13 | ), 14 | ], 15 | } as Meta 16 | 17 | const Template = (args) => ( 18 |
19 | 20 |
21 | ) 22 | 23 | export const Loading_Small = Template.bind({}) 24 | Loading_Small.args = { 25 | title: 'Loading', 26 | className: 'w-3 h-3', 27 | } 28 | 29 | export const LoadingDefault = Template.bind({}) 30 | LoadingDefault.args = { 31 | title: 'Loading', 32 | className: 'w-5 h-5', 33 | } 34 | 35 | export const Loading_Large = Template.bind({}) 36 | Loading_Large.args = { 37 | title: 'Loading', 38 | className: 'w-10 h-10', 39 | } 40 | -------------------------------------------------------------------------------- /hasura/migrations/1625684913027_create_table_public_list_items/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "public"."list_items" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "body" text NOT NULL, "updated_at" timestamptz NOT NULL DEFAULT now(), "created_at" timestamptz NOT NULL DEFAULT now(), "title" text, "url" text, "imageUrl" text, "publishedAt" timestamptz, PRIMARY KEY ("id") ); 2 | CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"() 3 | RETURNS TRIGGER AS $$ 4 | DECLARE 5 | _new record; 6 | BEGIN 7 | _new := NEW; 8 | _new."updated_at" = NOW(); 9 | RETURN _new; 10 | END; 11 | $$ LANGUAGE plpgsql; 12 | CREATE TRIGGER "set_public_list_items_updated_at" 13 | BEFORE UPDATE ON "public"."list_items" 14 | FOR EACH ROW 15 | EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"(); 16 | COMMENT ON TRIGGER "set_public_list_items_updated_at" ON "public"."list_items" 17 | IS 'trigger to set value of column "updated_at" to current timestamp on row update'; 18 | -------------------------------------------------------------------------------- /src/utils/docs.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { join } from 'path' 3 | import matter from 'gray-matter' 4 | 5 | const docsDirectory = join(process.cwd(), 'src', 'docs') 6 | 7 | type MetaData = { 8 | title: string 9 | description: string 10 | image_url?: string 11 | date?: Date | string 12 | } 13 | 14 | export type MetaTypes = { 15 | slug: string 16 | meta: MetaData 17 | content: string 18 | } 19 | 20 | export const getDocBySlug = (slug: string): MetaTypes => { 21 | const realSlug = slug.replace(/\.md$/, '') 22 | const fullPath = join(docsDirectory, `${realSlug}.md`) 23 | 24 | const fileContents = fs.readFileSync(fullPath, 'utf8') 25 | 26 | const { data, content } = matter(fileContents) 27 | 28 | return { slug: realSlug, meta: data as MetaData, content } 29 | } 30 | 31 | export const getAllDocs = () => { 32 | const slugs = fs.readdirSync(docsDirectory) 33 | const docs = slugs.map((slug) => getDocBySlug(slug)) 34 | 35 | return docs 36 | } 37 | -------------------------------------------------------------------------------- /src/components/showErrorAlert.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Swal from 'sweetalert2' 3 | import withReactContent from 'sweetalert2-react-content' 4 | 5 | export const showErrorAlert = ({ 6 | title, 7 | text, 8 | error, 9 | }: { 10 | title?: string 11 | text?: string 12 | footerText?: string 13 | error?: any 14 | }) => { 15 | const Alerta = withReactContent(Swal) 16 | 17 | let textFinal = text 18 | if (!textFinal) { 19 | textFinal = error?.message 20 | textFinal = textFinal?.replace(/\\n/g, '\n') 21 | } 22 | 23 | return Alerta.fire({ 24 | title:

{title || 'erro'}

, 25 | html: ( 26 |
30 | {text || (error?.message && textFinal)} 31 |
32 | ), 33 | showCloseButton: true, 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /src/components/List_items_Form/List_items_Form.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Story, Meta } from '@storybook/react/types-6-0' 3 | import { List_items_Form, List_items_FormProps } from './List_items_Form' 4 | 5 | export default { 6 | title: 'Component/Pages/List_items_Form', 7 | component: List_items_Form, 8 | } as Meta 9 | 10 | const Template: Story = (args) => 11 | 12 | export const List_items_Form_Empty = Template.bind({}) 13 | List_items_Form_Empty.args = {} 14 | 15 | export const List_items_Form_Filled = Template.bind({}) 16 | List_items_Form_Filled.args = { 17 | initialFormData: { 18 | title: 'Some Title', 19 | body: 'My incredible body', 20 | url: 'https://altphotos.com/photo/lovely-brunette-covers-her-mouth-behind-the-leaf-1882/', 21 | imageUrl: 'https://media.altphotos.com/cache/images/2017/07/13/07/752/woman-hat-portrait.jpg', 22 | publishedAt: '2021-07-08T11:41:00+00:00', 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/middleware/logMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from 'next' 2 | import { headerFormatter, logger } from '../logger' 3 | import omit from 'lodash/omit' 4 | 5 | export const logMiddleware = (handler: (req: NextApiRequest, res: NextApiResponse) => void) => { 6 | return async (req: NextApiRequest, res: NextApiResponse) => { 7 | const enviroment = process.env.NODE_ENV 8 | 9 | if (enviroment === 'production') { 10 | return handler(req, res) 11 | } 12 | 13 | const headers = headerFormatter(req.headers) 14 | 15 | const headerWithoutCookie = omit(headers, ['cookie']) 16 | 17 | const { body } = req 18 | 19 | logger.debug( 20 | { 21 | request: { 22 | headers: headerWithoutCookie, 23 | url: req.url, 24 | method: req.method, 25 | }, 26 | input: { 27 | body, 28 | }, 29 | }, 30 | handler.name 31 | ) 32 | 33 | return handler(req, res) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/pages/index.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-extra-semi */ 2 | import React from 'react' 3 | import * as TestingLib from '@testing-library/react' 4 | import '@testing-library/jest-dom/extend-expect' 5 | import Page from '../../src/pages' 6 | import { useUser } from '@auth0/nextjs-auth0' 7 | import { useRouter } from 'next/router' 8 | 9 | jest.mock('@auth0/nextjs-auth0') 10 | jest.mock('next/router') 11 | 12 | global.fetch = require('node-fetch') 13 | 14 | describe('Home page', () => { 15 | it('should render home page with user signed out', () => { 16 | // mock useUser 17 | 18 | ;(useRouter as jest.Mock).mockReturnValue({ 19 | asPath: '', 20 | }) 21 | ;(useUser as jest.Mock).mockReturnValue({ 22 | user: { picture: 'http://daisyui.com/tailwind-css-component-profile-1@94w.png' }, 23 | }) 24 | const PageWithProvider = 25 | const { getByText } = TestingLib.render(PageWithProvider) 26 | 27 | const eleList = getByText('Code Generator') 28 | expect(eleList).toBeInTheDocument() 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import Link from 'next/link' 3 | 4 | export function Header() { 5 | return ( 6 |
7 | 18 | 23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2021 Julio Saito 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Elitizon Ltd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/components/helpers/ChangeThemeLayout.tsx: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import { useTheme } from 'next-themes' 3 | import React, { ReactNode } from 'react' 4 | import { ThemeList } from '../../model/site/ThemeList' 5 | 6 | interface ChangeThemeLayoutProps { 7 | children: ReactNode 8 | } 9 | 10 | export function ChangeThemeLayout({ children }: ChangeThemeLayoutProps) { 11 | const { theme, setTheme } = useTheme() 12 | return ( 13 |
14 |
15 |
16 | 30 |
31 |
32 | {children} 33 |
34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/tailwind.css' 2 | import '../styles/sweetalert.css' 3 | import { ThemeProvider } from 'next-themes' 4 | import { StrictMode } from 'react' 5 | import { QueryClient, QueryClientProvider } from 'react-query' 6 | import { UserProvider } from '@auth0/nextjs-auth0' 7 | import { ReactQueryDevtools } from 'react-query/devtools' 8 | import type { AppProps } from 'next/app' 9 | 10 | // dayjs 11 | import dayjs from 'dayjs' 12 | import 'dayjs/locale/pt-br' 13 | import utc from 'dayjs/plugin/utc' 14 | import timezone from 'dayjs/plugin/timezone' 15 | 16 | dayjs.extend(utc) 17 | dayjs.extend(timezone) 18 | dayjs.tz.setDefault('America/Sao_Paulo') 19 | 20 | function MyApp({ Component, pageProps }: AppProps) { 21 | const queryClient = new QueryClient() 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ) 34 | } 35 | 36 | export default MyApp 37 | -------------------------------------------------------------------------------- /src/pages/api/typed-fetch-examples/fetch_tester_api_post.ts: -------------------------------------------------------------------------------- 1 | import { withSentry } from '@sentry/nextjs' 2 | import { NextApiRequest, NextApiResponse } from 'next' 3 | import { Fetch_tester_api_post } from '../../../model/api-models/typed-fetch-examples/Fetch_tester_api_post' 4 | import { HttpStatusCode } from '../../../utils/typedFetch/HttpStatusCode' 5 | 6 | export default withSentry(async function fetch_tester_api_post( 7 | req: NextApiRequest, 8 | res: NextApiResponse 9 | ) { 10 | // input data 11 | const inputData = req.body as Fetch_tester_api_post['input'] 12 | 13 | // force_error 14 | if (inputData.force_error === true) { 15 | throw new Error('SOME SERVER ERROR ON POST') 16 | } 17 | 18 | // process 19 | const result = 10 / inputData.divide_by 20 | const finalMessage = `Your string <${inputData.some_string}> has ${inputData.some_string.length} letters` 21 | 22 | await new Promise((resolve) => { 23 | setTimeout(resolve, 1000) 24 | }) 25 | 26 | // output data 27 | const output: Fetch_tester_api_post['output'] = { 28 | message: finalMessage, 29 | division_result: result, 30 | } 31 | res.status(HttpStatusCode.OK_200).json(output) 32 | }) 33 | -------------------------------------------------------------------------------- /src/pages/api/typed-fetch-examples/fetch_tester_api_get.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from 'next' 2 | import toNumber from 'lodash/toNumber' 3 | import { Fetch_tester_api_get } from '../../../model/api-models/typed-fetch-examples/Fetch_tester_api_get' 4 | import { HttpStatusCode } from '../../../utils/typedFetch/HttpStatusCode' 5 | import { withSentry } from '@sentry/nextjs' 6 | 7 | export default withSentry(async function fetch_tester_api_get( 8 | req: NextApiRequest, 9 | res: NextApiResponse 10 | ) { 11 | // input data 12 | const inputData = req.query as Fetch_tester_api_get['input'] 13 | 14 | // force_error 15 | if (inputData.force_error === 'true') { 16 | throw new Error('SOME SERVER ERROR ON GET') 17 | } 18 | 19 | // process 20 | const result = 10 / toNumber(inputData.divide_by) 21 | const finalMessage = `Your string <${inputData.some_string}> has ${inputData.some_string.length} letters` 22 | 23 | await new Promise((resolve) => { 24 | setTimeout(resolve, 1000) 25 | }) 26 | 27 | // output data 28 | const output: Fetch_tester_api_get['output'] = { 29 | message: finalMessage, 30 | division_result: result, 31 | } 32 | res.status(HttpStatusCode.OK_200).json(output) 33 | }) 34 | -------------------------------------------------------------------------------- /src/model/schemas/FormExampleValidationSchema.ts: -------------------------------------------------------------------------------- 1 | import * as z from 'zod' 2 | import isEmail from 'validator/lib/isEmail' 3 | import { EMPTY_SELECT_OPTION_VALUE } from '../../components/forms/FormSelect/FormSelect' 4 | import isHexColor from 'validator/lib/isHexColor' 5 | 6 | export const ImageFormExample = z 7 | .object({ 8 | image_url: z.string(), 9 | image: typeof window === 'undefined' ? z.any() : z.instanceof(FileList), 10 | }) 11 | .partial() 12 | .refine((data) => !!data.image_url || !!data.image[0], { 13 | message: 'Select an image', 14 | path: ['image'], 15 | }) 16 | 17 | export const FormExampleValidationSchema = z 18 | .object({ 19 | email: z 20 | .string() 21 | .min(5) 22 | .refine((value) => isEmail(value), { 23 | message: 'invalid email', 24 | }), 25 | 26 | color_select: z.string().refine((value) => value !== EMPTY_SELECT_OPTION_VALUE, { 27 | message: 'please, select an option', 28 | }), 29 | color_input: z 30 | .string() 31 | .min(7) 32 | .refine((value) => isHexColor(value), { 33 | message: 'invalid color', 34 | }), 35 | toggle: z.boolean(), 36 | }) 37 | .and(ImageFormExample) 38 | -------------------------------------------------------------------------------- /src/components/forms/FormTextarea/FormTextarea.tsx: -------------------------------------------------------------------------------- 1 | import classnames from 'classnames' 2 | import React from 'react' 3 | import { FormBaseProps } from '../FormBaseProps' 4 | import { FormLabel } from '../FormLabel' 5 | 6 | export const FormTextarea: React.FC = ({ 7 | label, 8 | name, 9 | register, 10 | defaultValue, 11 | validationErrors, 12 | className, 13 | labelDescription, 14 | disabled, 15 | }) => { 16 | return ( 17 |
18 |
19 | 20 |