├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── config.yml │ ├── docs.yml │ ├── feature_request.yml │ └── other.yml └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── apps ├── README.md ├── backend │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── api │ │ │ ├── api.module.ts │ │ │ ├── auth.controller.ts │ │ │ ├── billing.controller.ts │ │ │ ├── categories.controller.ts │ │ │ ├── faq.controller.ts │ │ │ ├── integrations.controller.ts │ │ │ ├── invite.controller.ts │ │ │ ├── public │ │ │ │ ├── public.categories.controller.ts │ │ │ │ ├── public.faq.controller.ts │ │ │ │ ├── public.organization.controller.ts │ │ │ │ └── public.style.controller.ts │ │ │ ├── settings.controller.ts │ │ │ ├── stripe.controller.ts │ │ │ ├── styles.controller.ts │ │ │ └── users.controller.ts │ │ ├── app.module.ts │ │ ├── main.ts │ │ └── services │ │ │ ├── auth.middleware.ts │ │ │ ├── authorization │ │ │ ├── authorization.ability.ts │ │ │ ├── authorization.guard.ts │ │ │ ├── authorization.service.ts │ │ │ └── subscription.exception.ts │ │ │ ├── generator.service.ts │ │ │ ├── public.middleware.ts │ │ │ ├── response.interceptor.ts │ │ │ ├── revalidate.ts │ │ │ └── services.module.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── webpack.config.js ├── communication │ ├── .eslintrc.json │ ├── jest.config.ts │ ├── project.json │ ├── railway.toml │ ├── src │ │ ├── app.module.ts │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── create.provider.ts │ │ ├── list │ │ │ ├── discord │ │ │ │ ├── commands │ │ │ │ │ ├── discord.add.command.ts │ │ │ │ │ └── discord.invite.command.ts │ │ │ │ └── provider.ts │ │ │ └── slack │ │ │ │ ├── commands │ │ │ │ ├── slack.add.command.ts │ │ │ │ └── slack.invite.command.ts │ │ │ │ └── provider.ts │ │ └── main.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── webpack.config.js ├── docs │ ├── README.md │ ├── _snippets │ │ └── snippet-example.mdx │ ├── api-reference │ │ ├── custom │ │ │ ├── authentications │ │ │ │ ├── check-org-and-user.mdx │ │ │ │ ├── login-to-meetfaq.mdx │ │ │ │ └── register-to-meetfaq.mdx │ │ │ ├── billings │ │ │ │ ├── get-billing.mdx │ │ │ │ ├── get-billingcheck.mdx │ │ │ │ ├── post-billingcancel.mdx │ │ │ │ ├── post-billingmodify.mdx │ │ │ │ └── subscribe-to-a-plan.mdx │ │ │ ├── categories │ │ │ │ ├── delete-categories.mdx │ │ │ │ ├── get-categories-1.mdx │ │ │ │ ├── get-categories.mdx │ │ │ │ ├── get-categoriesfaq.mdx │ │ │ │ ├── post-categories.mdx │ │ │ │ ├── post-categoriesorder.mdx │ │ │ │ └── put-categories.mdx │ │ │ ├── faq │ │ │ │ ├── delete-faq.mdx │ │ │ │ ├── get-faq.mdx │ │ │ │ ├── get-faqjobs.mdx │ │ │ │ ├── post-faq.mdx │ │ │ │ ├── post-faqanswers.mdx │ │ │ │ ├── post-faqjob.mdx │ │ │ │ ├── post-faqjobsquestions.mdx │ │ │ │ ├── post-faqorder.mdx │ │ │ │ ├── put-faq-category.mdx │ │ │ │ └── put-faq.mdx │ │ │ ├── integrations │ │ │ │ ├── delete-integrations.mdx │ │ │ │ ├── get-integrations-add.mdx │ │ │ │ ├── get-integrations.mdx │ │ │ │ └── post-integrations.mdx │ │ │ ├── invitations │ │ │ │ ├── post-invite.mdx │ │ │ │ └── post-inviteadd.mdx │ │ │ ├── settings │ │ │ │ ├── delete-settingsdelete-domain.mdx │ │ │ │ ├── get-settings.mdx │ │ │ │ ├── get-settingscheck-domain.mdx │ │ │ │ ├── post-settingscheck-subdomain.mdx │ │ │ │ ├── post-settingsdomain.mdx │ │ │ │ └── post-settingssubdomain.mdx │ │ │ ├── stripe │ │ │ │ └── post-stripe.mdx │ │ │ ├── styles │ │ │ │ ├── get-styles.mdx │ │ │ │ └── put-styles.mdx │ │ │ └── users │ │ │ │ ├── get-usersself.mdx │ │ │ │ ├── get-userssign-in.mdx │ │ │ │ └── post-usersrevalidate.mdx │ │ └── introduction.mdx │ ├── essentials │ │ ├── code.mdx │ │ ├── images.mdx │ │ ├── markdown.mdx │ │ ├── navigation.mdx │ │ └── settings.mdx │ ├── favicon.png │ ├── howitworks.mdx │ ├── images │ │ ├── arch.png │ │ ├── checks-passed.png │ │ ├── hero-dark.svg │ │ └── hero-light.svg │ ├── introduction.mdx │ ├── logo │ │ ├── dark.png │ │ └── light.png │ ├── mint.js │ ├── mint.json │ ├── openapi.json │ ├── project.json │ ├── public-api-reference │ │ ├── custom │ │ │ └── public │ │ │ │ ├── categories-list.mdx │ │ │ │ ├── faq-list.mdx │ │ │ │ ├── get-organization-style.mdx │ │ │ │ └── retrieve-faq.mdx │ │ └── introduction.mdx │ ├── quickstart.mdx │ └── test.js ├── marketing │ ├── .eslintrc.json │ ├── components │ │ ├── Button.tsx │ │ ├── Card.tsx │ │ ├── Container.tsx │ │ ├── Faq.tsx │ │ ├── Faqs.tsx │ │ ├── Feature.tsx │ │ ├── Features.tsx │ │ ├── FooterSection.tsx │ │ ├── HeaderSection.tsx │ │ ├── HeroSection.tsx │ │ ├── Princing.tsx │ │ ├── Products.tsx │ │ ├── Reviews.tsx │ │ ├── Solution.tsx │ │ └── SolutionContent.tsx │ ├── index.d.ts │ ├── jest.config.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── pages │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ ├── global.css │ │ ├── index.tsx │ │ ├── privacy-policy.tsx │ │ └── terms-of-service.tsx │ ├── postcss.config.js │ ├── project.json │ ├── public │ │ ├── .gitkeep │ │ ├── action.mp4 │ │ ├── favicon.ico │ │ ├── images │ │ │ ├── api.svg │ │ │ ├── clients │ │ │ │ ├── airbnb.svg │ │ │ │ ├── firecamp.svg │ │ │ │ ├── flagsmith.svg │ │ │ │ ├── ge.svg │ │ │ │ ├── google-cloud.svg │ │ │ │ ├── google.svg │ │ │ │ ├── keep.png │ │ │ │ ├── llmonitor.svg │ │ │ │ ├── microsoft.svg │ │ │ │ ├── netflix.svg │ │ │ │ └── trigger.svg │ │ │ ├── discord.svg │ │ │ ├── hero-dark.webp │ │ │ ├── hero.webp │ │ │ ├── illus-dark.webp │ │ │ ├── illus.webp │ │ │ ├── integrations.svg │ │ │ ├── milestone-dark.webp │ │ │ ├── page.png │ │ │ ├── search.svg │ │ │ ├── security.webp │ │ │ ├── style.svg │ │ │ ├── supportman.png │ │ │ ├── wizard.png │ │ │ └── xp.webp │ │ ├── logo.png │ │ └── og.png │ ├── tailwind.config.js │ ├── tsconfig.json │ └── tsconfig.spec.json ├── panel │ ├── .eslintrc.json │ ├── index.d.ts │ ├── jest.config.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── postcss.config.js │ ├── project.json │ ├── public │ │ ├── .gitkeep │ │ ├── bg.svg │ │ ├── favicon.ico │ │ ├── favicon.svg │ │ ├── integrations │ │ │ ├── discord.png │ │ │ └── slack.png │ │ ├── logobot.png │ │ └── usericon.png │ ├── src │ │ ├── app │ │ │ ├── (auth) │ │ │ │ └── auth │ │ │ │ │ ├── layout.tsx │ │ │ │ │ ├── login │ │ │ │ │ └── page.tsx │ │ │ │ │ └── register │ │ │ │ │ ├── [token] │ │ │ │ │ └── page.tsx │ │ │ │ │ └── page.tsx │ │ │ ├── (dashboard) │ │ │ │ ├── billing │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── faqs │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── integrations │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── job │ │ │ │ │ └── [id] │ │ │ │ │ │ ├── loading.tsx │ │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── settings │ │ │ │ │ └── page.tsx │ │ │ │ ├── style │ │ │ │ │ ├── loading.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── success-add │ │ │ │ │ └── page.tsx │ │ │ │ └── visit │ │ │ │ │ └── page.tsx │ │ │ ├── api │ │ │ │ ├── integrations │ │ │ │ │ └── [type] │ │ │ │ │ │ └── route.ts │ │ │ │ └── join │ │ │ │ │ └── [token] │ │ │ │ │ └── route.ts │ │ │ └── styles.css │ │ ├── components │ │ │ ├── auth │ │ │ │ ├── login.component.tsx │ │ │ │ └── registration.component.tsx │ │ │ ├── billing │ │ │ │ └── billing.component.tsx │ │ │ ├── categories │ │ │ │ ├── categories.select.component.tsx │ │ │ │ ├── change.category.tsx │ │ │ │ └── create.category.tsx │ │ │ ├── faqs │ │ │ │ ├── create.faq.tsx │ │ │ │ ├── delete.category.tsx │ │ │ │ └── faq.component.tsx │ │ │ ├── icons │ │ │ │ ├── bell.icon.tsx │ │ │ │ ├── billing.icon.tsx │ │ │ │ ├── delete.component.tsx │ │ │ │ ├── edit.component.tsx │ │ │ │ ├── exit.icon.tsx │ │ │ │ ├── faq.icon.tsx │ │ │ │ ├── integrations.icon.tsx │ │ │ │ ├── logout.icon.tsx │ │ │ │ ├── move.component.tsx │ │ │ │ ├── org.settings.tsx │ │ │ │ ├── settings.icon.tsx │ │ │ │ ├── small.arrow.down.tsx │ │ │ │ ├── style.component.tsx │ │ │ │ └── warning.icon.tsx │ │ │ ├── integrations │ │ │ │ ├── create.integration.tsx │ │ │ │ └── integration.component.tsx │ │ │ ├── jobs │ │ │ │ └── job.component.tsx │ │ │ ├── layout │ │ │ │ ├── layout.component.tsx │ │ │ │ ├── layout.load.tsx │ │ │ │ ├── side.menu.tsx │ │ │ │ └── toaster.tsx │ │ │ ├── loading │ │ │ │ └── loading.component.tsx │ │ │ ├── messages │ │ │ │ ├── message.wizard.tsx │ │ │ │ └── messages.component.tsx │ │ │ ├── payment │ │ │ │ └── payment.tsx │ │ │ ├── settings │ │ │ │ ├── api.component.tsx │ │ │ │ ├── check.domain.component.tsx │ │ │ │ ├── domains │ │ │ │ │ ├── components │ │ │ │ │ │ ├── configured-section-placeholder.tsx │ │ │ │ │ │ ├── configured-section.tsx │ │ │ │ │ │ ├── domain-card-placeholder.tsx │ │ │ │ │ │ ├── domain-card.tsx │ │ │ │ │ │ ├── loading-dots.module.css │ │ │ │ │ │ └── loading-dots.tsx │ │ │ │ │ └── domains.tsx │ │ │ │ ├── settings.component.tsx │ │ │ │ └── subdomain.component.tsx │ │ │ ├── style │ │ │ │ └── style.component.tsx │ │ │ ├── user │ │ │ │ └── user.component.tsx │ │ │ └── utils │ │ │ │ ├── block.tsx │ │ │ │ ├── button.tsx │ │ │ │ ├── div.modal.tsx │ │ │ │ ├── input.tsx │ │ │ │ ├── nice.modal.provider.tsx │ │ │ │ ├── tooltip.wrapper.tsx │ │ │ │ └── user.token.ts │ │ ├── helpers │ │ │ ├── delete.dialog.tsx │ │ │ ├── fetch.context.tsx │ │ │ ├── fetcher.tsx │ │ │ ├── load.all.tsx │ │ │ ├── revalidate.domain.ts │ │ │ ├── title.helper.tsx │ │ │ ├── user.context.tsx │ │ │ ├── wrap.meta.tsx │ │ │ └── wrap.modal.tsx │ │ ├── middleware.ts │ │ └── pages │ │ │ └── api │ │ │ └── upload.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ └── tsconfig.spec.json └── tenants │ ├── .eslintrc.json │ ├── index.d.ts │ ├── jest.config.ts │ ├── next-env.d.ts │ ├── next.config.js │ ├── postcss.config.js │ ├── project.json │ ├── public │ ├── .gitkeep │ ├── favicon.ico │ ├── favicon.svg │ ├── logobot.png │ └── usericon.png │ ├── src │ ├── app │ │ ├── (customers) │ │ │ └── [customer] │ │ │ │ ├── categories │ │ │ │ └── [slug] │ │ │ │ │ └── page.tsx │ │ │ │ ├── faq │ │ │ │ └── [slug] │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ └── page.tsx │ │ ├── api │ │ │ └── revalidate │ │ │ │ └── route.ts │ │ └── styles.css │ ├── components │ │ ├── claim │ │ │ └── claim.this.page.component.tsx │ │ ├── search │ │ │ └── search.component.tsx │ │ └── utils │ │ │ ├── after.highlight.tsx │ │ │ └── button.tsx │ ├── helpers │ │ ├── get.api.key.tsx │ │ └── text.to.markdown.ts │ └── middleware.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ └── tsconfig.spec.json ├── jest.config.ts ├── jest.preset.js ├── libraries ├── database │ ├── .eslintrc.json │ ├── .gitignore │ ├── README.md │ ├── jest.config.ts │ ├── prisma │ │ └── schema.prisma │ ├── project.json │ ├── src │ │ ├── categories │ │ │ ├── category.module.ts │ │ │ ├── category.repository.ts │ │ │ └── category.service.ts │ │ ├── domains │ │ │ ├── domain.module.ts │ │ │ ├── domain.repository.ts │ │ │ └── domain.service.ts │ │ ├── faq │ │ │ ├── faq.module.ts │ │ │ ├── faq.repository.ts │ │ │ └── faq.service.ts │ │ ├── integrations │ │ │ ├── integrations.module.ts │ │ │ ├── integrations.repository.ts │ │ │ └── integrations.service.ts │ │ ├── jobs │ │ │ ├── jobs.module.ts │ │ │ ├── jobs.repository.ts │ │ │ └── jobs.service.ts │ │ ├── organization │ │ │ ├── organization.module.ts │ │ │ ├── organization.repository.ts │ │ │ └── organization.service.ts │ │ ├── prisma.module.ts │ │ ├── prisma.service.ts │ │ ├── settings │ │ │ ├── settings.module.ts │ │ │ ├── settings.repository.ts │ │ │ └── settings.service.ts │ │ ├── subscription │ │ │ ├── subscription.module.ts │ │ │ ├── subscription.repository.ts │ │ │ └── subscription.service.ts │ │ └── users │ │ │ ├── user.module.ts │ │ │ ├── user.repository.ts │ │ │ └── user.service.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── helpers │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── project.json │ ├── src │ │ ├── algolia │ │ │ └── algolia.service.ts │ │ ├── auth │ │ │ └── auth.service.ts │ │ ├── components │ │ │ ├── code.block.component.tsx │ │ │ ├── editor.tsx │ │ │ └── load.all.tsx │ │ ├── fetchObject │ │ │ └── custom.fetch.backend.ts │ │ ├── makeid │ │ │ └── make.id.ts │ │ ├── pricing │ │ │ └── pricing.ts │ │ ├── revalidate │ │ │ └── revalidate.service.ts │ │ ├── slack │ │ │ ├── slack.props.ts │ │ │ └── slack.service.ts │ │ ├── stripe │ │ │ └── stripe.service.ts │ │ ├── subdomain │ │ │ ├── all.two.level.subdomain.ts │ │ │ └── subdomain.management.ts │ │ ├── swagger │ │ │ └── load.swagger.ts │ │ └── user │ │ │ ├── organization.from.request.ts │ │ │ ├── user.from.request.ts │ │ │ └── user.interface.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json └── validators │ ├── .eslintrc.json │ ├── README.md │ ├── jest.config.ts │ ├── project.json │ ├── src │ ├── auth │ │ ├── auth.integration.validator.ts │ │ ├── login.validator.ts │ │ └── registration.validator.ts │ ├── billing │ │ └── billing.subscribe.validator.ts │ ├── categories │ │ ├── create.category.validator.ts │ │ └── delete.category.validator.ts │ ├── faq │ │ └── create.faq.validator.ts │ ├── general │ │ ├── category.string.validator.ts │ │ ├── id.string.validator.ts │ │ └── order.validator.ts │ ├── integrations │ │ ├── create.integration.validator.ts │ │ └── integrations.type.validator.ts │ ├── invitations │ │ └── create.invitations.validator.ts │ ├── messages │ │ └── messages.list.validator.ts │ ├── organizations │ │ ├── organization.create.validator.ts │ │ └── update.style.validator.ts │ ├── public │ │ └── domain.subDomain.organization.validator.ts │ └── settings │ │ ├── add.domain.validator.ts │ │ └── check.subdomain.validator.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ └── tsconfig.spec.json ├── migrations.json ├── nx.json ├── package-lock.json ├── package.json └── tsconfig.base.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DISCORD_CLIENT= 2 | DISCORD_SECRET= 3 | DISCORD_TOKEN= 4 | DATABASE_URL= 5 | BACKEND_URL=http://localhost:3000 6 | NEXT_PUBLIC_BACKEND_URL=http://localhost:3000 7 | BACKEND_TOKEN_PROTECTOR= 8 | OPENAI_API_KEY= 9 | JWT_SECRET= 10 | FRONTEND_URL=http://localhost:4200 11 | TENANTS_URL=http://localhost:4202 12 | BLOB_READ_WRITE_TOKEN= 13 | PROJECT_ID_VERCEL= 14 | TEAM_ID_VERCEL= 15 | PAYMENT_PUBLIC_KEY= 16 | PAYMENT_SECRET_KEY= 17 | PAYMENT_SIGNING_SECRET= 18 | MARKETING_WEBSITE_URL=http://localhost:4201 19 | REVALIDATE_URL=http://localhost:4202/api/revalidate 20 | NEXT_PUBLIC_ALGOLIA_APP_ID= 21 | NEXT_PUBLIC_ALGOLIA_SEARCH_KEY= 22 | ALGOLIA_ADMIN_API_KEY= 23 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nx/dependency-checks": "error", 10 | "@typescript-eslint/no-non-null-asserted-optional-chain": "off", 11 | "@typescript-eslint/no-explicit-any": "off", 12 | "@nx/enforce-module-boundaries": [ 13 | "error", 14 | { 15 | "enforceBuildableLibDependency": true, 16 | "allow": [], 17 | "depConstraints": [ 18 | { 19 | "sourceTag": "*", 20 | "onlyDependOnLibsWithTags": ["*"] 21 | } 22 | ] 23 | } 24 | ] 25 | } 26 | }, 27 | { 28 | "files": ["*.ts", "*.tsx"], 29 | "extends": ["plugin:@nx/typescript"], 30 | "rules": {} 31 | }, 32 | { 33 | "files": ["*.js", "*.jsx"], 34 | "extends": ["plugin:@nx/javascript"], 35 | "rules": {} 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug 2 | description: Report an issue to help improve the project. 3 | labels: ["bug"] 4 | body: 5 | - type: textarea 6 | id: description 7 | attributes: 8 | label: Description 9 | description: A brief description of the question or issue, also include what you tried and what didn't work 10 | validations: 11 | required: true 12 | - type: textarea 13 | id: screenshots 14 | attributes: 15 | label: Screenshots 16 | description: Please add screenshots if applicable 17 | validations: 18 | required: false 19 | - type: textarea 20 | id: extrainfo 21 | attributes: 22 | label: Additional information 23 | description: Is there anything else we should know about this bug? 24 | validations: 25 | required: false 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs.yml: -------------------------------------------------------------------------------- 1 | name: 📄 Documentation issue 2 | description: Found an issue in the documentation? You can use this one! 3 | title: "[DOCS] " 4 | labels: ["documentation"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: A brief description of the question or issue, also include what you tried and what didn't work 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: screenshots 15 | attributes: 16 | label: Screenshots 17 | description: Please add screenshots if applicable 18 | validations: 19 | required: false 20 | - type: textarea 21 | id: extrainfo 22 | attributes: 23 | label: Additional information 24 | description: Is there anything else we should know about this issue? 25 | validations: 26 | required: false 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 💡 General Feature Request 2 | description: Have a new idea/feature for crosspublic? Please suggest! 3 | title: "[FEATURE] " 4 | labels: ["enhancement"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: A brief description of the enhancement you propose, also include what you tried and what worked. 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: screenshots 15 | attributes: 16 | label: Screenshots 17 | description: Please add screenshots if applicable 18 | validations: 19 | required: false 20 | - type: textarea 21 | id: extrainfo 22 | attributes: 23 | label: Additional information 24 | description: Is there anything else we should know about this idea? 25 | validations: 26 | required: false 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.yml: -------------------------------------------------------------------------------- 1 | name: Other 2 | description: Use this for any other issues. Please do NOT create blank issues 3 | title: "[OTHER] " 4 | 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: "# Other issue" 9 | - type: textarea 10 | id: issuedescription 11 | attributes: 12 | label: What would you like to share? 13 | description: Provide a clear and concise explanation of your issue. 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: extrainfo 18 | attributes: 19 | label: Additional information 20 | description: Is there anything else we should know about this issue? 21 | validations: 22 | required: false 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | --- 5 | ## Issue Ticket Number 6 | Fixes #(issue_number) 7 | 8 | --- 9 | ## Type of change 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 14 | - [ ] This change requires a documentation update 15 | 16 | --- 17 | # Checklist: 18 | - [ ] I have followed the contributing guidelines of this project as mentioned in [CONTRIBUTING.md](/CONTRIBUTING.md) 19 | - [ ] I have checked to ensure there aren't other open [Pull Requests](https://github.com/github-20k/crosspublic/pulls) for the same update/change? 20 | - [ ] I have performed a self-review of my own code 21 | - [ ] I have commented my code, particularly in hard-to-understand areas 22 | - [ ] I have made corresponding changes needed to the documentation 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | apps/backend/src/metadata.ts 4 | # compiled output 5 | dist 6 | tmp 7 | /out-tsc 8 | 9 | # dependencies 10 | node_modules 11 | .env 12 | # IDEs and editors 13 | /.idea 14 | .project 15 | .classpath 16 | .c9/ 17 | *.launch 18 | .settings/ 19 | *.sublime-workspace 20 | 21 | # IDE - VSCode 22 | .vscode/* 23 | !.vscode/settings.json 24 | !.vscode/tasks.json 25 | !.vscode/launch.json 26 | !.vscode/extensions.json 27 | 28 | # misc 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | 42 | .nx/cache 43 | 44 | # Next.js 45 | .next 46 | .vercel 47 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | /dist 3 | /coverage 4 | /.nx/cache -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /apps/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | Novu 6 | 7 | 8 |
9 | 10 |
11 | 12 |

Support Channels into a Public FAQ with AI

13 | 14 |
15 | Not all of your customers are on your support channels.
16 | Extract your customer's conversation into an FAQ to make it available to everyone. 17 |
18 | 19 |

20 |
21 | Explore the docs » 22 |

23 | Register to the cloud 24 | · 25 | Join our Discord 26 |

27 | 28 | ## ⭐️ What is this folder? 29 | 30 | The /apps folder contains all the projects of the monorepo. 31 | 32 | You can read more about it here: 33 | https://docs.crosspublic.com/howitworks 34 | 35 | - `backend` - This it the backend that is being used by all the services. 36 | - `discord` - This is the Discord bot that is being used to extract the conversations. 37 | - `panel` - This is the dashboard that is being used by the customers. 38 | - `tanants` - This is the website that is being used by the customers. 39 | - `marketing` - This is the marketing website. 40 | - `docs` - This is the documentation website powered by Mintlify. 41 | 42 | 43 | -------------------------------------------------------------------------------- /apps/backend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "rules": { 5 | "@nx/enforce-module-boundaries": "off", 6 | "@typescript-eslint/no-explicit-any": "off" 7 | }, 8 | "overrides": [ 9 | { 10 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.ts", "*.tsx"], 15 | "rules": {} 16 | }, 17 | { 18 | "files": ["*.js", "*.jsx"], 19 | "rules": {} 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /apps/backend/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'backend', 4 | preset: '../../jest.preset.js', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 8 | }, 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageDirectory: '../../coverage/apps/backend', 11 | }; 12 | -------------------------------------------------------------------------------- /apps/backend/src/api/public/public.faq.controller.ts: -------------------------------------------------------------------------------- 1 | import {Controller, Get, Param} from "@nestjs/common"; 2 | import {GetOrganizationFromRequest, OrganizationFromRequest} from "@crosspublic/helpers/src/user/organization.from.request"; 3 | import slugify from "slugify"; 4 | import {FaqService} from "@crosspublic/database/src/faq/faq.service"; 5 | import {ApiHeaders, ApiOperation, ApiTags} from "@nestjs/swagger"; 6 | 7 | @ApiTags('Public') 8 | @Controller('/public/faq') 9 | @ApiHeaders([{name: 'apikey', required: true}]) 10 | export class PublicFaqController { 11 | constructor( 12 | private _faqService: FaqService, 13 | ) { 14 | } 15 | 16 | @Get('/:slug') 17 | @ApiOperation({summary: 'Retrieve FAQ', description: "Get FAQ by slug \n\n **[WARNING]**: This endpoint requires the PRO package"}) 18 | async getFaqBySlug( 19 | @Param('slug') slug: string, 20 | @GetOrganizationFromRequest() organization: OrganizationFromRequest 21 | ) { 22 | const faq = (await this._faqService.getFaqsByOrganizationId(organization.id)).find(faq => slugify(faq.title, {lower: true, strict: true}) === slug); 23 | 24 | return { 25 | ...faq, 26 | categories: { 27 | ...faq.categories.map((category) => ({ 28 | category: { 29 | ...category.category, 30 | slug: slugify(category.category.name, {lower: true, strict: true}) 31 | } 32 | })) 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /apps/backend/src/api/public/public.organization.controller.ts: -------------------------------------------------------------------------------- 1 | import {Controller, Get, Headers, Query} from "@nestjs/common"; 2 | import {DomainSubDomainOrganizationValidator} from "@crosspublic/validators/src/public/domain.subDomain.organization.validator"; 3 | import {OrganizationService} from "@crosspublic/database/src/organization/organization.service"; 4 | import {ApiExcludeEndpoint, ApiTags} from "@nestjs/swagger"; 5 | 6 | @ApiTags('Public') 7 | @Controller('/public/organization') 8 | export class PublicOrganizationController { 9 | constructor( 10 | private _organizationService: OrganizationService, 11 | ) { 12 | } 13 | @Get('/') 14 | @ApiExcludeEndpoint() 15 | async organization( 16 | @Query() query: DomainSubDomainOrganizationValidator, 17 | @Headers('serverkey') serverkey: string 18 | ) { 19 | if (serverkey !== process.env.BACKEND_TOKEN_PROTECTOR) { 20 | return 401; 21 | } 22 | return this._organizationService.getOrganizationByDomainSubDomain(query); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/backend/src/api/public/public.style.controller.ts: -------------------------------------------------------------------------------- 1 | import {Controller, Get} from "@nestjs/common"; 2 | import {OrganizationService} from "@crosspublic/database/src/organization/organization.service"; 3 | import {ApiHeaders, ApiOperation, ApiTags} from "@nestjs/swagger"; 4 | import {GetOrganizationFromRequest, OrganizationFromRequest} from "@crosspublic/helpers/src/user/organization.from.request"; 5 | 6 | @ApiTags('Public') 7 | @Controller('/public/styles') 8 | @ApiHeaders([{name: 'apikey', required: true}]) 9 | export class PublicStyleController { 10 | constructor( 11 | private _organizationService: OrganizationService, 12 | ) { 13 | } 14 | 15 | @Get('/') 16 | @ApiOperation({summary: 'Get organization style', description: "Get organization style \n\n **[WARNING]**: This endpoint requires the PRO package"}) 17 | async getOrganizationStyle( 18 | @GetOrganizationFromRequest() organization: OrganizationFromRequest 19 | ) { 20 | const { 21 | name, 22 | topBarColor, 23 | topBarTextColor, 24 | backgroundColor, 25 | pageTextColor, 26 | pageBlockColor, 27 | brandingText 28 | } = await this._organizationService.getOrganizationStyle(organization.id); 29 | 30 | return { 31 | name, 32 | topBarColor, 33 | topBarTextColor, 34 | backgroundColor, 35 | pageTextColor, 36 | pageBlockColor, 37 | brandingText, 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /apps/backend/src/api/stripe.controller.ts: -------------------------------------------------------------------------------- 1 | import {Controller, Post, RawBodyRequest, Req} from "@nestjs/common"; 2 | import {StripeService} from "@crosspublic/helpers/src/stripe/stripe.service"; 3 | import {ApiHeaders, ApiTags} from "@nestjs/swagger"; 4 | 5 | @ApiTags('Stripe') 6 | @Controller('/stripe') 7 | @ApiHeaders([{name: 'auth', required: true}]) 8 | export class StripeController { 9 | constructor( 10 | private readonly _stripeService: StripeService 11 | ) { 12 | } 13 | @Post('/') 14 | stripe( 15 | @Req() req: RawBodyRequest 16 | ) { 17 | const event = this._stripeService.validateRequest( 18 | req.rawBody, 19 | req.headers['stripe-signature'], 20 | process.env.PAYMENT_SIGNING_SECRET 21 | ); 22 | 23 | // Maybe it comes from another stripe webhook 24 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 25 | // @ts-ignore 26 | if (event?.data?.object?.metadata?.service !== 'crosspublic') { 27 | return {ok: true}; 28 | } 29 | 30 | switch (event.type) { 31 | case 'customer.subscription.created': 32 | return this._stripeService.createSubscription(event); 33 | case 'customer.subscription.updated': 34 | return this._stripeService.updateSubscription(event); 35 | case 'customer.subscription.deleted': 36 | return this._stripeService.deleteSubscription(event); 37 | default: 38 | return {ok: true}; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /apps/backend/src/api/users.controller.ts: -------------------------------------------------------------------------------- 1 | import {Controller, Get, Post} from "@nestjs/common"; 2 | import {GetUserFromRequest} from "@crosspublic/helpers/src/user/user.from.request"; 3 | import {UserInterface} from "@crosspublic/helpers/src/user/user.interface"; 4 | import {OrganizationService} from "@crosspublic/database/src/organization/organization.service"; 5 | import * as process from "process"; 6 | import {RevalidateService} from "@crosspublic/helpers/src/revalidate/revalidate.service"; 7 | import {ApiHeaders, ApiTags} from "@nestjs/swagger"; 8 | 9 | @ApiTags('Users') 10 | @Controller('/users') 11 | @ApiHeaders([{name: 'auth', required: true}]) 12 | export class UsersController { 13 | constructor( 14 | private _organizationService: OrganizationService, 15 | private _revalidateService: RevalidateService 16 | ) { 17 | } 18 | @Get('/sign-in') 19 | async signIn() { 20 | const url = process.env.FRONTEND_URL; 21 | return {url}; 22 | } 23 | 24 | @Get('/self') 25 | getUser(@GetUserFromRequest() user: UserInterface) { 26 | return user; 27 | } 28 | 29 | @Post('/revalidate') 30 | async revalidate(@GetUserFromRequest() user: UserInterface) { 31 | const domainSubDomain = await this._organizationService.getOrganizationDomainSubdomainByOrgId(user.organization.organizationId); 32 | if (!domainSubDomain) { 33 | return ; 34 | } 35 | const name = domainSubDomain?.domains?.[0]?.domain || domainSubDomain?.subDomain; 36 | this._revalidateService.revalidate(name); 37 | return user; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /apps/backend/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import {Global, Module} from "@nestjs/common"; 2 | import {PrismaModule} from "@crosspublic/database/src/prisma.module"; 3 | import {ApiModule} from "@crosspublic/backend/src/api/api.module"; 4 | import {ServicesModule} from "@crosspublic/backend/src/services/services.module"; 5 | import {APP_GUARD} from "@nestjs/core"; 6 | import {PoliciesGuard} from "@crosspublic/backend/src/services/authorization/authorization.guard"; 7 | 8 | @Global() 9 | @Module({ 10 | imports: [ApiModule, PrismaModule, ServicesModule], 11 | controllers: [], 12 | providers: [ 13 | { 14 | provide: APP_GUARD, 15 | useClass: PoliciesGuard 16 | } 17 | ] 18 | }) 19 | export class AppModule { 20 | 21 | } 22 | -------------------------------------------------------------------------------- /apps/backend/src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is not a production server yet! 3 | * This is only a minimal backend to get started. 4 | */ 5 | 6 | import {Logger, ValidationPipe} from '@nestjs/common'; 7 | import { NestFactory } from '@nestjs/core'; 8 | import {AppModule} from "@crosspublic/backend/src/app.module"; 9 | import cookieParser from 'cookie-parser'; 10 | import {SubscriptionExceptionFilter} from "@crosspublic/backend/src/services/authorization/subscription.exception"; 11 | import morgan from 'morgan'; 12 | import {ResponseInterceptor} from "@crosspublic/backend/src/services/response.interceptor"; 13 | import * as process from "process"; 14 | import {loadSwagger} from "@crosspublic/helpers/src/swagger/load.swagger"; 15 | 16 | async function bootstrap() { 17 | const app = await NestFactory.create(AppModule, { 18 | rawBody: true, 19 | cors: { 20 | allowedHeaders: ['Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'Authorization', 'apikey', 'serverkey'], 21 | credentials: true, 22 | methods: ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'DELETE'], 23 | origin: [process.env.FRONTEND_URL, process.env.MARKETING_WEBSITE_URL], 24 | } 25 | }); 26 | 27 | app.use(cookieParser()); 28 | app.use(morgan(':method :url')); 29 | app.useGlobalPipes(new ValidationPipe({ 30 | transform: true 31 | })); 32 | app.useGlobalFilters(new SubscriptionExceptionFilter()); 33 | app.useGlobalInterceptors(new ResponseInterceptor()); 34 | 35 | loadSwagger(app); 36 | 37 | const port = process.env.PORT || 3000; 38 | await app.listen(port); 39 | Logger.log( 40 | `🚀 Application is running on: http://localhost:${port}` 41 | ); 42 | } 43 | 44 | bootstrap(); 45 | -------------------------------------------------------------------------------- /apps/backend/src/services/auth.middleware.ts: -------------------------------------------------------------------------------- 1 | import {NestMiddleware} from "@nestjs/common"; 2 | import { Request, Response, NextFunction } from 'express'; 3 | import {AuthService} from "@crosspublic/helpers/src/auth/auth.service"; 4 | 5 | export class AuthMiddleware implements NestMiddleware { 6 | async use(req: Request, res: Response, next: NextFunction) { 7 | if(req.url.indexOf('/public') > -1) { 8 | next(); 9 | return; 10 | } 11 | 12 | if (!req.headers.auth && !req.cookies.auth) { 13 | res.status(401).json({ message: 'Unauthorized' }); 14 | return; 15 | } 16 | 17 | try { 18 | const testJwt = AuthService.verifyJWT((req.headers.auth || req.cookies.auth) as string); 19 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 20 | // @ts-ignore 21 | req.user = testJwt; 22 | next(); 23 | } 24 | catch (err) { 25 | res.status(401).json({ message: 'Unauthorized' }); 26 | return; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /apps/backend/src/services/authorization/authorization.ability.ts: -------------------------------------------------------------------------------- 1 | import {SetMetadata} from "@nestjs/common"; 2 | import {AuthorizationActions, Sections} from "@crosspublic/backend/src/services/authorization/authorization.service"; 3 | 4 | export const CHECK_POLICIES_KEY = 'check_policy'; 5 | export type AbilityPolicy = [AuthorizationActions, Sections]; 6 | export const CheckPolicies = (...handlers: AbilityPolicy[]) => SetMetadata(CHECK_POLICIES_KEY, handlers); 7 | -------------------------------------------------------------------------------- /apps/backend/src/services/authorization/subscription.exception.ts: -------------------------------------------------------------------------------- 1 | import {ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus} from "@nestjs/common"; 2 | import {AuthorizationActions, Sections} from "@crosspublic/backend/src/services/authorization/authorization.service"; 3 | 4 | export class SubscriptionException extends HttpException { 5 | constructor(message: { 6 | section: Sections, 7 | action: AuthorizationActions 8 | }) { 9 | super(message, HttpStatus.PAYMENT_REQUIRED); 10 | } 11 | } 12 | 13 | @Catch(SubscriptionException) 14 | export class SubscriptionExceptionFilter implements ExceptionFilter { 15 | catch(exception: HttpException, host: ArgumentsHost) { 16 | const ctx = host.switchToHttp(); 17 | const response = ctx.getResponse(); 18 | const status = exception.getStatus(); 19 | const error: {section: Sections, action: AuthorizationActions} = exception.getResponse() as any; 20 | 21 | const message = getErrorMessage(error); 22 | 23 | response.status(status).json({ 24 | statusCode: status, 25 | message, 26 | url: process.env.FRONTEND_URL + '/billing', 27 | }); 28 | } 29 | } 30 | 31 | const getErrorMessage = (error: {section: Sections, action: AuthorizationActions}) => { 32 | switch (error.section) { 33 | case Sections.FAQ: 34 | switch (error.action) { 35 | default: 36 | return 'You have reached the maximum number of FAQ\'s for your subscription. Please upgrade your subscription to add more FAQ\'s.'; 37 | } 38 | case Sections.CATEGORY: 39 | switch (error.action) { 40 | default: 41 | return 'You have reached the maximum number of categories for your subscription. Please upgrade your subscription to add more categories.'; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /apps/backend/src/services/generator.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from "@nestjs/common"; 2 | import OpenAI from "openai"; 3 | import Handlebars from "handlebars"; 4 | 5 | const openai = new OpenAI({ 6 | apiKey: process.env.OPENAI_API_KEY, 7 | }); 8 | 9 | @Injectable() 10 | export class GeneratorService { 11 | async create(prompt: Array<{prompt: string, role: 'system' | 'user' | 'assistant', variables: object}>, globalVariables = {}, model = 'gpt-4-1106-preview') { 12 | const messages = prompt.map(p => { 13 | return {role: p.role, content: Handlebars.compile(p.prompt)({...globalVariables, ...p.variables})}; 14 | }); 15 | 16 | return openai.chat.completions.create({ 17 | messages, 18 | model 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/backend/src/services/public.middleware.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, NestMiddleware} from "@nestjs/common"; 2 | import { Request, Response, NextFunction } from 'express'; 3 | import {OrganizationService} from "@crosspublic/database/src/organization/organization.service"; 4 | import {pricing} from "@crosspublic/helpers/src/pricing/pricing"; 5 | import * as process from "process"; 6 | 7 | @Injectable() 8 | export class PublicMiddleware implements NestMiddleware { 9 | constructor( 10 | private _organizationService: OrganizationService, 11 | ) { 12 | } 13 | async use(req: Request, res: Response, next: NextFunction) { 14 | if(req.url.indexOf('/public') === -1 || req.url.indexOf('/public/organization') > -1) { 15 | next(); 16 | return; 17 | } 18 | 19 | const apiKey = req.headers['apikey']; 20 | const serverKey = req.headers['serverkey']; 21 | 22 | if (!apiKey) { 23 | res.status(401).send('Unauthorized'); 24 | return; 25 | } 26 | 27 | const organization = await this._organizationService.getOrganizationByApiKey(apiKey as string); 28 | if (!organization) { 29 | res.status(401).json({message: 'Organization not found'}); 30 | return; 31 | } 32 | 33 | const isApi = pricing[organization?.subscriptions[0]?.subscriptionTier]?.api; 34 | if (!isApi && serverKey !== process.env.BACKEND_TOKEN_PROTECTOR) { 35 | res.status(401).json({message: 'Please upgrade your subscription to use this feature'}); 36 | return; 37 | } 38 | 39 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 40 | // @ts-ignore 41 | req.organization = organization; 42 | next(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /apps/backend/src/services/response.interceptor.ts: -------------------------------------------------------------------------------- 1 | import {Injectable, NestInterceptor, ExecutionContext, CallHandler} from '@nestjs/common'; 2 | import {Observable} from 'rxjs'; 3 | import { tap } from 'rxjs/operators'; 4 | import axios from "axios"; 5 | 6 | @Injectable() 7 | export class ResponseInterceptor implements NestInterceptor { 8 | async intercept(context: ExecutionContext, next: CallHandler): Promise> { 9 | return next 10 | .handle() 11 | .pipe( 12 | tap(async () => { 13 | const request = context.switchToHttp().getRequest(); 14 | if (request.user && request.revalidate) { 15 | return axios.post(`${process.env.BACKEND_URL}/users/revalidate`, {}, { 16 | headers: { 17 | auth: request.cookies.auth 18 | } 19 | }); 20 | } 21 | }), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /apps/backend/src/services/revalidate.ts: -------------------------------------------------------------------------------- 1 | import { Reflector } from '@nestjs/core'; 2 | 3 | export const Revalidate = Reflector.createDecorator({ 4 | transform: () => { 5 | return true; 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /apps/backend/src/services/services.module.ts: -------------------------------------------------------------------------------- 1 | import {Global, Module} from '@nestjs/common'; 2 | import {GeneratorService} from "@crosspublic/backend/src/services/generator.service"; 3 | import {AuthorizationService} from "@crosspublic/backend/src/services/authorization/authorization.service"; 4 | import {PoliciesGuard} from "@crosspublic/backend/src/services/authorization/authorization.guard"; 5 | import {StripeService} from "@crosspublic/helpers/src/stripe/stripe.service"; 6 | import {PublicMiddleware} from "@crosspublic/backend/src/services/public.middleware"; 7 | import {ResponseInterceptor} from "@crosspublic/backend/src/services/response.interceptor"; 8 | import {OrganizationService} from "@crosspublic/database/src/organization/organization.service"; 9 | import {RevalidateService} from "@crosspublic/helpers/src/revalidate/revalidate.service"; 10 | import {AlgoliaService} from "@crosspublic/helpers/src/algolia/algolia.service"; 11 | 12 | @Global() 13 | @Module({ 14 | imports: [], 15 | controllers: [], 16 | providers: [ 17 | GeneratorService, 18 | AuthorizationService, 19 | PublicMiddleware, 20 | ResponseInterceptor, 21 | PoliciesGuard, 22 | StripeService, 23 | OrganizationService, 24 | RevalidateService, 25 | AlgoliaService 26 | ], 27 | get exports() { 28 | return this.providers; 29 | } 30 | }) 31 | export class ServicesModule {} 32 | -------------------------------------------------------------------------------- /apps/backend/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["node"], 7 | "emitDecoratorMetadata": true, 8 | "target": "es2021" 9 | }, 10 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], 11 | "include": ["src/**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /apps/backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "esModuleInterop": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/backend/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /apps/backend/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { composePlugins, withNx } = require('@nx/webpack'); 2 | 3 | // Nx plugins for webpack. 4 | module.exports = composePlugins(withNx(), (config) => { 5 | // Update the webpack config as needed here. 6 | // e.g. `config.plugins.push(new MyPlugin())` 7 | return config; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/communication/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "rules": { 5 | "@nx/enforce-module-boundaries": "off", 6 | "@typescript-eslint/no-explicit-any": "off", 7 | "no-case-declarations": "off" 8 | }, 9 | "overrides": [ 10 | { 11 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 12 | "rules": {} 13 | }, 14 | { 15 | "files": ["*.ts", "*.tsx"], 16 | "rules": {} 17 | }, 18 | { 19 | "files": ["*.js", "*.jsx"], 20 | "rules": {} 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /apps/communication/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'communication', 4 | preset: '../../jest.preset.js', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 8 | }, 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageDirectory: '../../coverage/apps/communication', 11 | }; 12 | -------------------------------------------------------------------------------- /apps/communication/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "communication", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/communication/src", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/webpack:webpack", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "target": "node", 13 | "compiler": "tsc", 14 | "outputPath": "dist/apps/communication", 15 | "main": "apps/communication/src/main.ts", 16 | "tsConfig": "apps/communication/tsconfig.app.json", 17 | "assets": ["apps/communication/src/assets"], 18 | "isolatedConfig": true, 19 | "webpackConfig": "apps/communication/webpack.config.js" 20 | }, 21 | "configurations": { 22 | "development": {}, 23 | "production": {} 24 | } 25 | }, 26 | "serve": { 27 | "executor": "@nx/js:node", 28 | "defaultConfiguration": "development", 29 | "options": { 30 | "buildTarget": "communication:build" 31 | }, 32 | "configurations": { 33 | "development": { 34 | "buildTarget": "communication:build:development" 35 | }, 36 | "production": { 37 | "buildTarget": "communication:build:production" 38 | } 39 | } 40 | }, 41 | "lint": { 42 | "executor": "@nx/eslint:lint", 43 | "outputs": ["{options.outputFile}"], 44 | "options": { 45 | "lintFilePatterns": ["apps/communication/**/*.ts"] 46 | } 47 | }, 48 | "test": { 49 | "executor": "@nx/jest:jest", 50 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 51 | "options": { 52 | "jestConfig": "apps/communication/jest.config.ts" 53 | } 54 | } 55 | }, 56 | "tags": [] 57 | } 58 | -------------------------------------------------------------------------------- /apps/communication/railway.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | builder = "nixpacks" 3 | buildCommand = "npm run generate-prisma && npx nx run communication:build:production" 4 | 5 | [deploy] 6 | startCommand = "node dist/apps/communication/main.js" 7 | restartPolicyType = "ON_FAILURE" 8 | -------------------------------------------------------------------------------- /apps/communication/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { SlackAddCommand } from "@crosspublic/communication/src/list/slack/commands/slack.add.command"; 3 | import { DiscordAddCommand } from "@crosspublic/communication/src/list/discord/commands/discord.add.command"; 4 | import { SlackInviteCommand } from "@crosspublic/communication/src/list/slack/commands/slack.invite.command"; 5 | import { DiscordInviteCommand } from "@crosspublic/communication/src/list/discord/commands/discord.invite.command"; 6 | 7 | @Module({ 8 | imports: [], 9 | controllers: [], 10 | providers: [SlackAddCommand, DiscordAddCommand, SlackInviteCommand, DiscordInviteCommand], 11 | }) 12 | export class AppModule {} 13 | -------------------------------------------------------------------------------- /apps/communication/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/communication/src/assets/.gitkeep -------------------------------------------------------------------------------- /apps/communication/src/list/discord/commands/discord.invite.command.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common"; 2 | import { ProviderInviteInterface } from "@crosspublic/communication/src/create.provider"; 3 | import { CacheType, ChatInputCommandInteraction, Client } from "discord.js"; 4 | 5 | @Injectable() 6 | export class DiscordInviteCommand implements ProviderInviteInterface, ChatInputCommandInteraction> { 7 | async handle(setup: Client, payload: ChatInputCommandInteraction): Promise<{ internalId: string; role: string; guild: string }> { 8 | const userInput = payload.options.get('user').user; 9 | const role = payload.options.get('role').value as string; 10 | 11 | return { 12 | internalId: userInput.id, 13 | role, 14 | guild: payload.guildId 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/communication/src/list/slack/commands/slack.invite.command.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@nestjs/common"; 2 | import { App, SlackShortcut, SlackShortcutMiddlewareArgs } from "@slack/bolt"; 3 | import { StringIndexed } from "@slack/bolt/dist/types/helpers"; 4 | import { WebClient } from "@slack/web-api"; 5 | import { ProviderInviteInterface } from "@crosspublic/communication/src/create.provider"; 6 | 7 | @Injectable() 8 | export class SlackInviteCommand implements ProviderInviteInterface, SlackShortcutMiddlewareArgs & {client: WebClient}> { 9 | async handle(setup: App, payload: SlackShortcutMiddlewareArgs & { client: WebClient }): Promise<{ internalId: string; role: string; guild: string }> { 10 | const team = await payload.client.team.info(); 11 | return { 12 | guild: team.team.id, internalId: payload.payload.user.id, role: "user" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /apps/communication/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from "@nestjs/core"; 2 | import { slackProvider } from "@crosspublic/communication/src/list/slack/provider"; 3 | import { AppModule } from "@crosspublic/communication/src/app.module"; 4 | import { discordProvider } from "@crosspublic/communication/src/list/discord/provider"; 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.createApplicationContext(AppModule); 8 | const providerList = [ 9 | slackProvider, 10 | discordProvider 11 | ] 12 | .find((p) => p.type === (process.env.PROVIDER || 'SLACK')); 13 | 14 | await providerList.run(app); 15 | } 16 | 17 | bootstrap(); 18 | -------------------------------------------------------------------------------- /apps/communication/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["node"], 7 | "emitDecoratorMetadata": true, 8 | "target": "es2021" 9 | }, 10 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], 11 | "include": ["src/**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /apps/communication/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ], 13 | "compilerOptions": { 14 | "esModuleInterop": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /apps/communication/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /apps/communication/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { composePlugins, withNx } = require('@nx/webpack'); 2 | 3 | // Nx plugins for webpack. 4 | module.exports = composePlugins(withNx(), (config) => { 5 | // Update the webpack config as needed here. 6 | // e.g. `config.plugins.push(new MyPlugin())` 7 | return config; 8 | }); 9 | -------------------------------------------------------------------------------- /apps/docs/README.md: -------------------------------------------------------------------------------- 1 | # Mintlify Starter Kit 2 | 3 | Click on `Use this template` to copy the Mintlify starter kit. The starter kit contains examples including 4 | 5 | - Guide pages 6 | - Navigation 7 | - Customizations 8 | - API Reference pages 9 | - Use of popular components 10 | 11 | ### Development 12 | 13 | Install the [Mintlify CLI](https://www.npmjs.com/package/mintlify) to preview the documentation changes locally. To install, use the following command 14 | 15 | ``` 16 | npm i -g mintlify 17 | ``` 18 | 19 | Run the following command at the root of your documentation (where mint.json is) 20 | 21 | ``` 22 | mintlify dev 23 | ``` 24 | 25 | ### Publishing Changes 26 | 27 | Install our Github App to autopropagate changes from youre repo to your deployment. Changes will be deployed to production automatically after pushing to the default branch. Find the link to install on your dashboard. 28 | 29 | #### Troubleshooting 30 | 31 | - Mintlify dev isn't running - Run `mintlify install` it'll re-install dependencies. 32 | - Page loads as a 404 - Make sure you are running in a folder with `mint.json` 33 | -------------------------------------------------------------------------------- /apps/docs/_snippets/snippet-example.mdx: -------------------------------------------------------------------------------- 1 | ## My Snippet 2 | 3 | This is an example of a reusable snippet 4 | -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/authentications/check-org-and-user.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /auth 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/authentications/login-to-meetfaq.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /auth/login 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/authentications/register-to-meetfaq.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /auth/registration 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/billings/get-billing.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /billing 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/billings/get-billingcheck.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /billing/check/{id} 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/billings/post-billingcancel.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /billing/cancel 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/billings/post-billingmodify.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /billing/modify 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/billings/subscribe-to-a-plan.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /billing/subscribe 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/categories/delete-categories.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: delete /categories/{id} 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/categories/get-categories-1.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /categories/{id} 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/categories/get-categories.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /categories 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/categories/get-categoriesfaq.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /categories/faq 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/categories/post-categories.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /categories 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/categories/post-categoriesorder.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /categories/order 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/categories/put-categories.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: put /categories/{id} 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/faq/delete-faq.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: delete /faq/{id} 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/faq/get-faq.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /faq/{id} 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/faq/get-faqjobs.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /faq/jobs/{id} 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/faq/post-faq.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /faq 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/faq/post-faqanswers.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /faq/answers 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/faq/post-faqjob.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /faq/job 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/faq/post-faqjobsquestions.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /faq/jobs/questions 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/faq/post-faqorder.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /faq/order 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/faq/put-faq-category.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: put /faq/{id}/category/{category} 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/faq/put-faq.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: put /faq/{id} 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/integrations/delete-integrations.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: delete /integrations/{id} 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/integrations/get-integrations-add.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /integrations/{type}/add 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/integrations/get-integrations.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /integrations 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/integrations/post-integrations.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /integrations 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/invitations/post-invite.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /invite 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/invitations/post-inviteadd.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /invite/add 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/settings/delete-settingsdelete-domain.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: delete /settings/delete-domain/{id} 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/settings/get-settings.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /settings 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/settings/get-settingscheck-domain.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /settings/check-domain/{id} 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/settings/post-settingscheck-subdomain.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /settings/check-subdomain 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/settings/post-settingsdomain.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /settings/domain 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/settings/post-settingssubdomain.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /settings/subDomain 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/stripe/post-stripe.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /stripe 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/styles/get-styles.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /styles 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/styles/put-styles.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: put /styles 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/users/get-usersself.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /users/self 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/users/get-userssign-in.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /users/sign-in 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/custom/users/post-usersrevalidate.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: post /users/revalidate 3 | --- -------------------------------------------------------------------------------- /apps/docs/api-reference/introduction.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Introduction' 3 | --- 4 | 5 | 6 | If you're not looking to build API reference documentation, you can delete 7 | this section by removing the api-reference folder. 8 | 9 | 10 | ## Welcome 11 | 12 | ## How to use the API 13 | There are two main options to use the API: 14 | 1. You can send an header called `auth` with the token you received when you logged in. 15 | 2. You can send a cookie called `auth` with the token you received when you logged in. 16 | 17 | The dashboard mainly uses the cookie method to authenticate requests.
18 | But if you want to test the API with Swagger or Postman, you can use the `auth` header. 19 | -------------------------------------------------------------------------------- /apps/docs/essentials/code.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Code Blocks' 3 | description: 'Display inline code and code blocks' 4 | icon: 'code' 5 | --- 6 | 7 | ## Basic 8 | 9 | ### Inline Code 10 | 11 | To denote a `word` or `phrase` as code, enclose it in backticks (`). 12 | 13 | ``` 14 | To denote a `word` or `phrase` as code, enclose it in backticks (`). 15 | ``` 16 | 17 | ### Code Block 18 | 19 | Use [fenced code blocks](https://www.markdownguide.org/extended-syntax/#fenced-code-blocks) by enclosing code in three backticks and follow the leading ticks with the programming language of your snippet to get syntax highlighting. Optionally, you can also write the name of your code after the programming language. 20 | 21 | ```java HelloWorld.java 22 | class HelloWorld { 23 | public static void main(String[] args) { 24 | System.out.println("Hello, World!"); 25 | } 26 | } 27 | ``` 28 | 29 | ````md 30 | ```java HelloWorld.java 31 | class HelloWorld { 32 | public static void main(String[] args) { 33 | System.out.println("Hello, World!"); 34 | } 35 | } 36 | ``` 37 | ```` 38 | -------------------------------------------------------------------------------- /apps/docs/essentials/images.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Images and Embeds' 3 | description: 'Add image, video, and other HTML elements' 4 | icon: 'image' 5 | --- 6 | 7 | 11 | 12 | ## Image 13 | 14 | ### Using Markdown 15 | 16 | The [markdown syntax](https://www.markdownguide.org/basic-syntax/#images) lets you add images using the following code 17 | 18 | ```md 19 | ![title](/path/image.jpg) 20 | ``` 21 | 22 | Note that the image file size must be less than 5MB. Otherwise, we recommend hosting on a service like [Cloudinary](https://cloudinary.com/) or [S3](https://aws.amazon.com/s3/). You can then use that URL and embed. 23 | 24 | ### Using Embeds 25 | 26 | To get more customizability with images, you can also use [embeds](/writing-content/embed) to add images 27 | 28 | ```html 29 | 30 | ``` 31 | 32 | ## Embeds and HTML elements 33 | 34 | 44 | 45 |
46 | 47 | 48 | 49 | Mintlify supports [HTML tags in Markdown](https://www.markdownguide.org/basic-syntax/#html). This is helpful if you prefer HTML tags to Markdown syntax, and lets you create documentation with infinite flexibility. 50 | 51 | 52 | 53 | ### iFrames 54 | 55 | Loads another HTML page within the document. Most commonly used for embedding videos. 56 | 57 | ```html 58 | 59 | ``` 60 | -------------------------------------------------------------------------------- /apps/docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/docs/favicon.png -------------------------------------------------------------------------------- /apps/docs/images/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/docs/images/arch.png -------------------------------------------------------------------------------- /apps/docs/images/checks-passed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/docs/images/checks-passed.png -------------------------------------------------------------------------------- /apps/docs/logo/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/docs/logo/dark.png -------------------------------------------------------------------------------- /apps/docs/logo/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/docs/logo/light.png -------------------------------------------------------------------------------- /apps/docs/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/docs", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "command": "node apps/docs/test.js", 9 | "options": { 10 | "type": "module" 11 | } 12 | }, 13 | "serve": { 14 | "command": "cd apps/docs && npx mintlify dev", 15 | "options": { 16 | "type": "module" 17 | } 18 | } 19 | }, 20 | "tags": [] 21 | } 22 | -------------------------------------------------------------------------------- /apps/docs/public-api-reference/custom/public/categories-list.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /public/categories 3 | --- -------------------------------------------------------------------------------- /apps/docs/public-api-reference/custom/public/faq-list.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /public/categories/{slug}/faqs 3 | --- -------------------------------------------------------------------------------- /apps/docs/public-api-reference/custom/public/get-organization-style.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /public/styles 3 | --- -------------------------------------------------------------------------------- /apps/docs/public-api-reference/custom/public/retrieve-faq.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | openapi: get /public/faq/{slug} 3 | --- -------------------------------------------------------------------------------- /apps/docs/public-api-reference/introduction.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'Introduction' 3 | description: 'How to use the public API' 4 | --- 5 | 6 | 7 | The Public API is only available for the PRO plan and the self-hosted plan. 8 | 9 | 10 | ## Why public API 11 | if you want to build a custom implementation of the FAQ page, or you want to use it in different service, you can use the public API to get the data. 12 | 13 | ## How to use the public API 14 | There are two main options to use the Public API: 15 | 1. For the **PRO plan**, go to [Settings](https://app.crosspublic.com/dashboard/settings) and copy the API key.
16 | For every request to the `/public` API URL send a header called `apikey` with the value of the API key. 17 | 18 | 2. For the **self-hosted plan**, you can also pass `serverkey` to bypass package check.
19 | The `serverkey` is the value of `BACKEND_TOKEN_PROTECTOR` in the `.env` file. 20 | 21 | Check the endpoints on the left for API reference. 22 | -------------------------------------------------------------------------------- /apps/docs/test.js: -------------------------------------------------------------------------------- 1 | const process = require('process') 2 | process.chdir(__dirname); 3 | const {writeFileSync, renameSync, rmdirSync, mkdirSync} = require("fs"); 4 | const { config } = require('dotenv'); 5 | const prod = require('./mint.js').default; 6 | config(); 7 | (async () => { 8 | const {generateOpenApiPages} = await import("@mintlify/scraping"); 9 | 10 | try { 11 | rmdirSync('./api-reference/custom', { recursive: true }); 12 | rmdirSync('./public-api-reference/custom', { recursive: true }); 13 | await new Promise((resolve) => setTimeout(resolve, 2000)); 14 | } catch (e) { 15 | } 16 | 17 | await generateOpenApiPages(process.env.BACKEND_URL + '/docs-json', true, 'api-reference/custom'); 18 | const generate = await generateOpenApiPages(process.env.BACKEND_URL + '/docs-json'); 19 | await new Promise((resolve) => setTimeout(resolve, 3000)); 20 | 21 | mkdirSync('./public-api-reference/custom', { recursive: true }); 22 | renameSync('./api-reference/custom/public', './public-api-reference/custom/public/'); 23 | 24 | prod.navigation.push(...generate.nav.map((item) => ({ 25 | ...item, 26 | pages: item.pages.map((page) => (page.indexOf('public') > -1 ? 'public-api-reference/custom/' : 'api-reference/custom/') + page) 27 | }))); 28 | 29 | writeFileSync('./mint.json', JSON.stringify(prod, null, 2)); 30 | const text = await (await fetch(process.env.BACKEND_URL + '/docs-json')).text(); 31 | writeFileSync('./openapi.json', text); 32 | })(); 33 | -------------------------------------------------------------------------------- /apps/marketing/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@nx/react-typescript", 4 | "next", 5 | "next/core-web-vitals", 6 | "../../.eslintrc.json" 7 | ], 8 | "ignorePatterns": ["!**/*", ".next/**/*"], 9 | "rules": { 10 | "@nx/enforce-module-boundaries": "off", 11 | "@typescript-eslint/no-non-null-asserted-optional-chain": "off", 12 | "@typescript-eslint/no-explicit-any": "off", 13 | "react/no-children-prop" : "off", 14 | "@typescript-eslint/ban-ts-comment": "off", 15 | "@typescript-eslint/no-var-requires": "off", 16 | "no-empty": "off" 17 | }, 18 | "overrides": [ 19 | { 20 | "files": ["*.*"], 21 | "rules": { 22 | "@next/next/no-html-link-for-pages": "off" 23 | } 24 | }, 25 | { 26 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 27 | "rules": { 28 | "@next/next/no-html-link-for-pages": ["error", "apps/website/pages"] 29 | } 30 | }, 31 | { 32 | "files": ["*.ts", "*.tsx"], 33 | "rules": {} 34 | }, 35 | { 36 | "files": ["*.js", "*.jsx"], 37 | "rules": {} 38 | }, 39 | { 40 | "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"], 41 | "env": { 42 | "jest": true 43 | } 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /apps/marketing/components/Button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | 5 | export default function Button(props: { Element: string; label: any; type?: string; emphasis: string; to?: string; ui: string }) { 6 | const { Element, label, type, emphasis, to, ui } = props; 7 | 8 | const spanElement = React.createElement("span", { 9 | className: (emphasis === "primary" ? "text-white" : "text-primary dark:text-white") + ' span', 10 | children: label, 11 | }); 12 | 13 | return React.createElement(Element, { 14 | href: to, 15 | className: ui + 16 | (emphasis === "primary" 17 | ? " py-3 bg-primary px-5 rounded-2xl text-xl font-bold " 18 | : " px-5 py-2 rounded-2xl border border-transparent bg-primary/10"), 19 | type: type, 20 | children: spanElement 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /apps/marketing/components/Card.tsx: -------------------------------------------------------------------------------- 1 | export default function Card({ children }: { children: any }) { 2 | return ( 3 |
4 |
5 | { children } 6 |
7 |
8 | ) 9 | } -------------------------------------------------------------------------------- /apps/marketing/components/Container.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | export default function Container({ children }) { 3 | return ( 4 |
5 | { children } 6 |
7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /apps/marketing/components/Feature.tsx: -------------------------------------------------------------------------------- 1 | export default function Feature({title, description, children}: {title: string; description: string, children: any}) { 2 | return ( 3 |
4 |
5 | {children} 6 |
7 |
8 |

9 | {title} 10 |

11 |

{description}

12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /apps/marketing/components/Features.tsx: -------------------------------------------------------------------------------- 1 | import Container from "./Container"; 2 | 3 | export default function Features() { 4 | return ( 5 |
6 | 7 |
8 |
9 |

10 | Never miss a client question again 11 |

12 |

13 | Prevent clients from churning by providing them with a public FAQ.
14 |

15 |
16 | 17 |
18 |
19 |
21 |
22 |
23 |
24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /apps/marketing/components/Solution.tsx: -------------------------------------------------------------------------------- 1 | import Container from "./Container" 2 | import Products from "./Products" 3 | import SolutionContent from "./SolutionContent" 4 | import Image from "next/image.js" 5 | 6 | export default function Solution() { 7 | return ( 8 |
9 | 10 |
11 |
12 | Wizard 13 | app milestone 14 |
15 | 16 |
17 |

Put your focus in one place

18 |

19 | Instead of going back and forward between your support channels and your FAQ, you can now focus on one place and delegate the work to your mods and customer success team. 20 |

21 |
22 |
23 | 24 | {/**/} 25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /apps/marketing/index.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module '*.svg' { 3 | const content: any; 4 | export const ReactComponent: any; 5 | export default content; 6 | } 7 | -------------------------------------------------------------------------------- /apps/marketing/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'marketing', 4 | preset: '../../jest.preset.js', 5 | transform: { 6 | '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', 7 | '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }], 8 | }, 9 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 10 | coverageDirectory: '../../coverage/apps/marketing', 11 | }; 12 | -------------------------------------------------------------------------------- /apps/marketing/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/marketing/next.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const { composePlugins, withNx } = require('@nx/next'); 5 | 6 | /** 7 | * @type {import('@nx/next/plugins/with-nx').WithNxOptions} 8 | **/ 9 | const nextConfig = { 10 | nx: { 11 | // Set this to true if you would like to use SVGR 12 | // See: https://github.com/gregberge/svgr 13 | svgr: false, 14 | }, 15 | redirects: async () => ([ 16 | { 17 | permanent: true, 18 | source: '/docs', 19 | destination: process.env.DOCS_URL || '', 20 | }, 21 | { 22 | permanent: true, 23 | source: '/register', 24 | destination: process.env.FRONTEND_URL || '', 25 | } 26 | ]), 27 | env: { 28 | DOCS_URL: process.env.DOCS_URL || '' 29 | } 30 | }; 31 | 32 | const plugins = [ 33 | // Add more Next.js plugins to this list if needed. 34 | withNx, 35 | ]; 36 | 37 | module.exports = composePlugins(...plugins)(nextConfig); 38 | 39 | -------------------------------------------------------------------------------- /apps/marketing/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import './global.css'; 2 | import type { AppProps } from 'next/app'; 3 | import React from "react"; 4 | import {Inter} from "next/font/google"; 5 | const inter = Inter({ subsets: ['latin'] }); 6 | import { DefaultSeo } from 'next-seo'; 7 | 8 | export default function App({ Component, pageProps }: AppProps) { 9 | return ( 10 |
11 | 32 | 33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /apps/marketing/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import {Head, Html, Main, NextScript} from "next/document"; 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /apps/marketing/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import HeaderSection from "@crosspublic/marketing/components/HeaderSection"; 2 | import Footer from "@crosspublic/marketing/components/FooterSection"; 3 | import HeroSection from "@crosspublic/marketing/components/HeroSection"; 4 | import Features from "@crosspublic/marketing/components/Features"; 5 | import Solution from "@crosspublic/marketing/components/Solution"; 6 | import Reviews from "@crosspublic/marketing/components/Reviews"; 7 | import Princing from "@crosspublic/marketing/components/Princing"; 8 | import { NextSeo } from "next-seo"; 9 | 10 | export default function RootLayout({ 11 | stars 12 | }: { 13 | stars: string 14 | }) { 15 | return ( 16 | <> 17 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 |
32 | 33 | ); 34 | } 35 | 36 | export const getStaticProps = async () => { 37 | const {stargazers_count} = await (await fetch(`https://api.github.com/repos/github-20k/crosspublic`)).json(); 38 | return { 39 | props: { 40 | stars: stargazers_count 41 | }, 42 | revalidate: 3600 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /apps/marketing/postcss.config.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: { 6 | config: join(__dirname, 'tailwind.config.js'), 7 | }, 8 | autoprefixer: {}, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /apps/marketing/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marketing", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/marketing", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/next:build", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "outputPath": "dist/apps/marketing", 13 | "postcssConfig": "apps/marketing/postcss.config.js" 14 | }, 15 | "configurations": { 16 | "development": { 17 | "outputPath": "apps/marketing" 18 | }, 19 | "production": {} 20 | } 21 | }, 22 | "serve": { 23 | "executor": "@nx/next:server", 24 | "defaultConfiguration": "development", 25 | "options": { 26 | "buildTarget": "marketing:build", 27 | "dev": true, 28 | "port": 4201 29 | }, 30 | "configurations": { 31 | "development": { 32 | "buildTarget": "marketing:build:development", 33 | "dev": true 34 | }, 35 | "production": { 36 | "buildTarget": "marketing:build:production", 37 | "dev": false 38 | } 39 | } 40 | }, 41 | "export": { 42 | "executor": "@nx/next:export", 43 | "options": { 44 | "buildTarget": "marketing:build:production" 45 | } 46 | }, 47 | "test": { 48 | "executor": "@nx/jest:jest", 49 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 50 | "options": { 51 | "jestConfig": "apps/marketing/jest.config.ts" 52 | } 53 | }, 54 | "lint": { 55 | "executor": "@nx/eslint:lint", 56 | "outputs": ["{options.outputFile}"], 57 | "options": { 58 | "lintFilePatterns": ["apps/marketing/**/*.{ts,tsx,js,jsx}"] 59 | } 60 | } 61 | }, 62 | "tags": [] 63 | } 64 | -------------------------------------------------------------------------------- /apps/marketing/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/.gitkeep -------------------------------------------------------------------------------- /apps/marketing/public/action.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/action.mp4 -------------------------------------------------------------------------------- /apps/marketing/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/favicon.ico -------------------------------------------------------------------------------- /apps/marketing/public/images/api.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /apps/marketing/public/images/clients/google.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/marketing/public/images/clients/keep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/images/clients/keep.png -------------------------------------------------------------------------------- /apps/marketing/public/images/clients/netflix.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /apps/marketing/public/images/hero-dark.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/images/hero-dark.webp -------------------------------------------------------------------------------- /apps/marketing/public/images/hero.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/images/hero.webp -------------------------------------------------------------------------------- /apps/marketing/public/images/illus-dark.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/images/illus-dark.webp -------------------------------------------------------------------------------- /apps/marketing/public/images/illus.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/images/illus.webp -------------------------------------------------------------------------------- /apps/marketing/public/images/integrations.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/marketing/public/images/milestone-dark.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/images/milestone-dark.webp -------------------------------------------------------------------------------- /apps/marketing/public/images/page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/images/page.png -------------------------------------------------------------------------------- /apps/marketing/public/images/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/marketing/public/images/security.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/images/security.webp -------------------------------------------------------------------------------- /apps/marketing/public/images/supportman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/images/supportman.png -------------------------------------------------------------------------------- /apps/marketing/public/images/wizard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/images/wizard.png -------------------------------------------------------------------------------- /apps/marketing/public/images/xp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/images/xp.webp -------------------------------------------------------------------------------- /apps/marketing/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/logo.png -------------------------------------------------------------------------------- /apps/marketing/public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/marketing/public/og.png -------------------------------------------------------------------------------- /apps/marketing/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const { createGlobPatternsForDependencies } = require('@nx/react/tailwind'); 2 | const { join } = require('path'); 3 | 4 | module.exports = { 5 | darkMode: 'class', 6 | content: [ 7 | join( 8 | __dirname, 9 | '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}' 10 | ), 11 | ...createGlobPatternsForDependencies(__dirname), 12 | ], 13 | theme: { 14 | extend: { 15 | colors: { 16 | primary: "#9333EA", 17 | secondary: "#ff7e33", 18 | info: "#0C63E7", 19 | dark: "#0A101E", 20 | darker: "#090E1A", 21 | }, 22 | clipPath: { 23 | basic: 'inset(1px 1px)' 24 | } 25 | }, 26 | }, 27 | plugins: [], 28 | }; 29 | -------------------------------------------------------------------------------- /apps/marketing/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "resolveJsonModule": true, 12 | "skipDefaultLibCheck": true, 13 | "isolatedModules": true, 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "types": [ 21 | "jest", 22 | "node" 23 | ] 24 | }, 25 | "include": [ 26 | "**/*.ts", 27 | "**/*.tsx", 28 | "**/*.js", 29 | "**/*.jsx", 30 | "next-env.d.ts", 31 | "../../apps/website/.next/types/**/*.ts", 32 | "../../apps/marketing/.next/types/**/*.ts" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /apps/marketing/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"], 7 | "jsx": "react" 8 | }, 9 | "include": [ 10 | "jest.config.ts", 11 | "src/**/*.test.ts", 12 | "src/**/*.spec.ts", 13 | "src/**/*.test.tsx", 14 | "src/**/*.spec.tsx", 15 | "src/**/*.test.js", 16 | "src/**/*.spec.js", 17 | "src/**/*.test.jsx", 18 | "src/**/*.spec.jsx", 19 | "src/**/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /apps/panel/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@nx/react-typescript", 4 | "next", 5 | "next/core-web-vitals", 6 | "../../.eslintrc.json" 7 | ], 8 | "ignorePatterns": ["!**/*", ".next/**/*"], 9 | "rules": { 10 | "@nx/enforce-module-boundaries": "off", 11 | "@typescript-eslint/no-non-null-asserted-optional-chain": "off", 12 | "@typescript-eslint/no-explicit-any": "off", 13 | "react/no-children-prop" : "off", 14 | "@typescript-eslint/ban-ts-comment": "off", 15 | "@typescript-eslint/no-var-requires": "off", 16 | "no-empty": "off" 17 | }, 18 | "overrides": [ 19 | { 20 | "files": ["*.*"], 21 | "rules": { 22 | "@next/next/no-html-link-for-pages": "off" 23 | } 24 | }, 25 | { 26 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 27 | "rules": { 28 | "@next/next/no-html-link-for-pages": ["error", "apps/panel/pages"] 29 | } 30 | }, 31 | { 32 | "files": ["*.ts", "*.tsx"], 33 | "rules": {} 34 | }, 35 | { 36 | "files": ["*.js", "*.jsx"], 37 | "rules": {} 38 | }, 39 | { 40 | "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"], 41 | "env": { 42 | "jest": true 43 | } 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /apps/panel/index.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module '*.svg' { 3 | const content: any; 4 | export const ReactComponent: any; 5 | export default content; 6 | } 7 | -------------------------------------------------------------------------------- /apps/panel/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'panel', 4 | preset: '../../jest.preset.js', 5 | transform: { 6 | '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', 7 | '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }], 8 | }, 9 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 10 | coverageDirectory: '../../coverage/apps/panel', 11 | }; 12 | -------------------------------------------------------------------------------- /apps/panel/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /apps/panel/next.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const { composePlugins, withNx } = require('@nx/next'); 5 | 6 | /** 7 | * @type {import('@nx/next/plugins/with-nx').WithNxOptions} 8 | **/ 9 | const nextConfig = { 10 | nx: { 11 | // Set this to true if you would like to use SVGR 12 | // See: https://github.com/gregberge/svgr 13 | svgr: false, 14 | }, 15 | env: { 16 | FRONTEND_URL: process.env.FRONTEND_URL || '', 17 | }, 18 | transpilePackages: ['react-syntax-highlighter', 'geist'], 19 | typescript: { 20 | ignoreBuildErrors: true, 21 | }, 22 | async redirects() { 23 | return [ 24 | { 25 | source: '/', 26 | destination: '/faqs', 27 | permanent: true 28 | } 29 | ] 30 | }, 31 | }; 32 | 33 | const plugins = [ 34 | // Add more Next.js plugins to this list if needed. 35 | withNx, 36 | ]; 37 | 38 | module.exports = composePlugins(...plugins)(nextConfig); 39 | -------------------------------------------------------------------------------- /apps/panel/postcss.config.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: { 6 | config: join(__dirname, 'tailwind.config.js'), 7 | }, 8 | autoprefixer: {}, 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /apps/panel/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "panel", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/panel", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/next:build", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "outputPath": "dist/apps/panel", 13 | "postcssConfig": "apps/panel/postcss.config.js" 14 | }, 15 | "configurations": { 16 | "development": { 17 | "outputPath": "apps/panel" 18 | }, 19 | "production": {} 20 | } 21 | }, 22 | "serve": { 23 | "executor": "@nx/next:server", 24 | "defaultConfiguration": "development", 25 | "options": { 26 | "buildTarget": "panel:build", 27 | "dev": true 28 | }, 29 | "configurations": { 30 | "development": { 31 | "buildTarget": "panel:build:development", 32 | "dev": true 33 | }, 34 | "production": { 35 | "buildTarget": "panel:build:production", 36 | "dev": false 37 | } 38 | } 39 | }, 40 | "export": { 41 | "executor": "@nx/next:export", 42 | "options": { 43 | "buildTarget": "panel:build:production" 44 | } 45 | }, 46 | "test": { 47 | "executor": "@nx/jest:jest", 48 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 49 | "options": { 50 | "jestConfig": "apps/panel/jest.config.ts" 51 | } 52 | }, 53 | "lint": { 54 | "executor": "@nx/eslint:lint", 55 | "outputs": ["{options.outputFile}"], 56 | "options": { 57 | "lintFilePatterns": ["apps/panel/**/*.{ts,tsx,js,jsx}"] 58 | } 59 | } 60 | }, 61 | "tags": [] 62 | } 63 | -------------------------------------------------------------------------------- /apps/panel/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/panel/public/.gitkeep -------------------------------------------------------------------------------- /apps/panel/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/panel/public/favicon.ico -------------------------------------------------------------------------------- /apps/panel/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/panel/public/integrations/discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/panel/public/integrations/discord.png -------------------------------------------------------------------------------- /apps/panel/public/integrations/slack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/panel/public/integrations/slack.png -------------------------------------------------------------------------------- /apps/panel/public/logobot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/panel/public/logobot.png -------------------------------------------------------------------------------- /apps/panel/public/usericon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/panel/public/usericon.png -------------------------------------------------------------------------------- /apps/panel/src/app/(auth)/auth/layout.tsx: -------------------------------------------------------------------------------- 1 | import '../../styles.css'; 2 | import 'react-toastify/dist/ReactToastify.css'; 3 | import {Metadata} from "next"; 4 | import { Inter } from 'next/font/google' 5 | 6 | // If loading a variable font, you don't need to specify the font weight 7 | const inter = Inter({ subsets: ['latin'] }) 8 | 9 | export const metadata: Metadata = { 10 | title: '', 11 | description: '', 12 | } 13 | 14 | export default async function AuthLayout({ children }: { children: React.ReactNode }) { 15 | return ( 16 | 17 | 18 |
19 |
20 |
21 | 22 |
crosspublic
23 |
24 |
25 | {children} 26 |
27 |
28 |
29 | 30 | 31 | ) 32 | } 33 | 34 | -------------------------------------------------------------------------------- /apps/panel/src/app/(auth)/auth/login/page.tsx: -------------------------------------------------------------------------------- 1 | import {Metadata} from "next"; 2 | import dynamic from "next/dynamic"; 3 | const LoginComponent = dynamic(() => import('@crosspublic/panel/src/components/auth/login.component'), { ssr: false }); 4 | 5 | export const metadata: Metadata = { 6 | title: 'crosspublic Login', 7 | description: '', 8 | } 9 | 10 | export default async function Registration() { 11 | return 12 | } 13 | -------------------------------------------------------------------------------- /apps/panel/src/app/(auth)/auth/register/[token]/page.tsx: -------------------------------------------------------------------------------- 1 | import {Metadata} from "next"; 2 | import dynamic from "next/dynamic"; 3 | const RegistrationComponent = dynamic(() => import('@crosspublic/panel/src/components/auth/registration.component'), { ssr: false }); 4 | export const metadata: Metadata = { 5 | title: 'crosspublic Registration', 6 | description: '', 7 | } 8 | 9 | export default async function Registration({params: {token}}: {params: {token: string}}) { 10 | return 11 | } 12 | -------------------------------------------------------------------------------- /apps/panel/src/app/(auth)/auth/register/page.tsx: -------------------------------------------------------------------------------- 1 | import {Metadata} from "next"; 2 | import dynamic from "next/dynamic"; 3 | const RegistrationComponent = dynamic(() => import('@crosspublic/panel/src/components/auth/registration.component'), { ssr: false }); 4 | export const metadata: Metadata = { 5 | title: 'crosspublic Registration', 6 | description: '', 7 | } 8 | 9 | export default async function Registration() { 10 | return 11 | } 12 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/billing/loading.tsx: -------------------------------------------------------------------------------- 1 | import {LoadingComponent} from "@crosspublic/panel/src/components/loading/loading.component"; 2 | 3 | export default function Loading() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/billing/page.tsx: -------------------------------------------------------------------------------- 1 | import {BillingComponent} from "@crosspublic/panel/src/components/billing/billing.component"; 2 | import {Metadata} from "next"; 3 | import {userToken} from "@crosspublic/panel/src/components/utils/user.token"; 4 | import {customFetchBackend} from "@crosspublic/helpers/src/fetchObject/custom.fetch.backend"; 5 | import {headers} from "next/dist/client/components/headers"; 6 | import {redirect} from "next/navigation"; 7 | 8 | export const metadata: Metadata = { 9 | title: 'Billing', 10 | description: '', 11 | } 12 | 13 | export default async function Billing({searchParams}: {searchParams?: {check?: string}}) { 14 | const {data: billing} = await customFetchBackend(userToken()).get('/billing'); 15 | const pricing = Boolean(headers().get('pricing') === 'true'); 16 | if (!pricing) { 17 | return redirect('/'); 18 | } 19 | return ( 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/faqs/loading.tsx: -------------------------------------------------------------------------------- 1 | import {LoadingComponent} from "@crosspublic/panel/src/components/loading/loading.component"; 2 | 3 | export default function Loading() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/faqs/page.tsx: -------------------------------------------------------------------------------- 1 | import {customFetchBackend} from "@crosspublic/helpers/src/fetchObject/custom.fetch.backend"; 2 | import dynamic from "next/dynamic"; 3 | import {userToken} from "@crosspublic/panel/src/components/utils/user.token"; 4 | import {Metadata} from "next"; 5 | const FaqComponent = dynamic(() => import('@crosspublic/panel/src/components/faqs/faq.component'), {ssr: false}); 6 | 7 | export const metadata: Metadata = { 8 | title: 'FAQ', 9 | description: '', 10 | } 11 | 12 | export default async function FAQs() { 13 | const {data} = await customFetchBackend(userToken()).get('/categories/faq'); 14 | return ( 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/integrations/loading.tsx: -------------------------------------------------------------------------------- 1 | import {LoadingComponent} from "@crosspublic/panel/src/components/loading/loading.component"; 2 | 3 | export default function Loading() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/integrations/page.tsx: -------------------------------------------------------------------------------- 1 | import {Metadata} from "next"; 2 | import {IntegrationComponent} from "@crosspublic/panel/src/components/integrations/integration.component"; 3 | import {customFetchBackend} from "@crosspublic/helpers/src/fetchObject/custom.fetch.backend"; 4 | import {userToken} from "@crosspublic/panel/src/components/utils/user.token"; 5 | 6 | export const metadata: Metadata = { 7 | title: 'Integrations', 8 | description: '', 9 | } 10 | 11 | export default async function Style() { 12 | const {data: integrations} = await customFetchBackend(userToken()).get(`/integrations`); 13 | return ( 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/job/[id]/loading.tsx: -------------------------------------------------------------------------------- 1 | import {LoadingComponent} from "@crosspublic/panel/src/components/loading/loading.component"; 2 | 3 | export default function Loading() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/job/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import {customFetchBackend} from "@crosspublic/helpers/src/fetchObject/custom.fetch.backend"; 2 | import {userToken} from "@crosspublic/panel/src/components/utils/user.token"; 3 | import {JobComponent} from "@crosspublic/panel/src/components/jobs/job.component"; 4 | import {Metadata} from "next"; 5 | import {Suspense} from "react"; 6 | 7 | export const metadata: Metadata = { 8 | title: 'Process a new job', 9 | description: '', 10 | } 11 | 12 | export default async function Job({ params }: { params: { id: string } }) { 13 | const {data: messages} = await customFetchBackend(userToken()).get(`/faq/jobs/${params?.id}`); 14 | const {data: categories} = await customFetchBackend(userToken()).get(`/categories`); 15 | 16 | return ( 17 | 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/layout.tsx: -------------------------------------------------------------------------------- 1 | import '../styles.css'; 2 | import 'react-tooltip/dist/react-tooltip.css'; 3 | import 'react-toastify/dist/ReactToastify.css'; 4 | import {LayoutComponent} from "@crosspublic/panel/src/components/layout/layout.component"; 5 | import {Metadata} from "next"; 6 | import { cookies, headers } from 'next/headers'; 7 | import { Inter } from 'next/font/google' 8 | import {fetchBackend} from "@crosspublic/helpers/src/fetchObject/custom.fetch.backend"; 9 | import {redirect} from "next/navigation"; 10 | 11 | // If loading a variable font, you don't need to specify the font weight 12 | const inter = Inter({ subsets: ['latin'] }) 13 | 14 | export const metadata: Metadata = { 15 | title: '', 16 | description: '', 17 | } 18 | 19 | export default async function DashboardLayout({ children }: { children: React.ReactNode, params: any }) { 20 | const cookiesList = cookies(); 21 | const cookieToken = cookiesList.get('auth')?.value; 22 | const headersToken = headers().get('auth'); 23 | 24 | if (!cookieToken && !headersToken) { 25 | return redirect('/auth/register'); 26 | } 27 | 28 | const response = await fetchBackend('/users/self', { 29 | headers: { 30 | auth: cookieToken || headersToken 31 | }, 32 | cache: 'force-cache', 33 | tags: [cookieToken || headersToken], 34 | next: { 35 | revalidate: 3600 36 | } 37 | }); 38 | 39 | if (response.status === 401) { 40 | return redirect('/auth/logout'); 41 | } 42 | 43 | return ( 44 | <> 45 | 46 | {children} 47 | 48 | 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import {SettingsComponent} from "@crosspublic/panel/src/components/settings/settings.component"; 2 | import {customFetchBackend} from "@crosspublic/helpers/src/fetchObject/custom.fetch.backend"; 3 | import {userToken} from "@crosspublic/panel/src/components/utils/user.token"; 4 | import {Metadata} from "next"; 5 | import {Suspense} from "react"; 6 | 7 | export const metadata: Metadata = { 8 | title: 'Settings', 9 | description: '', 10 | } 11 | 12 | export default async function Settings() { 13 | const {data: settings} = await customFetchBackend(userToken()).get(`/settings`); 14 | /* 15 | * Replace the elements below with your own. 16 | * 17 | * Note: The corresponding styles are in the ./index.scss file. 18 | */ 19 | return ( 20 | 21 | 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/style/loading.tsx: -------------------------------------------------------------------------------- 1 | import {LoadingComponent} from "@crosspublic/panel/src/components/loading/loading.component"; 2 | 3 | export default function Loading() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/style/page.tsx: -------------------------------------------------------------------------------- 1 | import {Metadata} from "next"; 2 | import {StyleComponent} from "@crosspublic/panel/src/components/style/style.component"; 3 | import {customFetchBackend} from "@crosspublic/helpers/src/fetchObject/custom.fetch.backend"; 4 | import {userToken} from "@crosspublic/panel/src/components/utils/user.token"; 5 | 6 | export const metadata: Metadata = { 7 | title: 'Style', 8 | description: '', 9 | } 10 | 11 | export default async function Style() { 12 | const {data: styles} = await customFetchBackend(userToken()).get(`/styles`); 13 | return ( 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/success-add/page.tsx: -------------------------------------------------------------------------------- 1 | import {Metadata} from "next"; 2 | import {Block} from "@crosspublic/panel/src/components/utils/block"; 3 | import {wrapMeta} from "@crosspublic/panel/src/helpers/wrap.meta"; 4 | 5 | export const metadata: Metadata = { 6 | title: 'You are in', 7 | description: '', 8 | } 9 | 10 | const YouAreIn = wrapMeta(() => { 11 | return ( 12 | 13 | You have successfully added to the organization. 14 | 15 | ) 16 | }) 17 | 18 | export default async function Settings() { 19 | /* 20 | * Replace the elements below with your own. 21 | * 22 | * Note: The corresponding styles are in the ./index.scss file. 23 | */ 24 | return ( 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /apps/panel/src/app/(dashboard)/visit/page.tsx: -------------------------------------------------------------------------------- 1 | // import { revalidatePath } from 'next/cache'; 2 | import { customFetchBackend } from "@crosspublic/helpers/src/fetchObject/custom.fetch.backend"; 3 | import { userToken } from "@crosspublic/panel/src/components/utils/user.token"; 4 | import { redirect } from "next/navigation"; 5 | 6 | export default async function Page() { 7 | const auth = userToken() 8 | if (!auth) { 9 | return <> 10 | } 11 | 12 | const {data} = await customFetchBackend(auth).get('/settings'); 13 | 14 | if (data?.domains?.length) { 15 | return redirect('https://' + data.domains[0].domain); 16 | } 17 | 18 | const url = new URL(process.env.TENANTS_URL!); 19 | 20 | const newURL = url.protocol + '//' + data.subDomain + '.' + new URL(process.env.TENANTS_URL!).host; 21 | return redirect(new URL('/', newURL).toString()); 22 | } 23 | -------------------------------------------------------------------------------- /apps/panel/src/app/api/integrations/[type]/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from 'next/server' 2 | import {fetchBackend} from "@crosspublic/helpers/src/fetchObject/custom.fetch.backend"; 3 | 4 | export async function GET(request: NextRequest, context: {params: {type: string}}) { 5 | const auth = request.cookies.get('auth')?.value; 6 | const information = Object.fromEntries(new URLSearchParams(request.nextUrl.searchParams)); 7 | 8 | await fetchBackend('/integrations', { 9 | method: 'POST', 10 | headers: { 11 | auth, 12 | 'Content-Type': 'application/json', 13 | }, 14 | body: JSON.stringify({ 15 | type: context.params.type, 16 | information: { 17 | ...information, 18 | __type: context.params.type 19 | } 20 | }), 21 | cache: 'no-cache' 22 | }); 23 | 24 | return Response.redirect(new URL('/integrations', request.url)); 25 | } 26 | -------------------------------------------------------------------------------- /apps/panel/src/app/api/join/[token]/route.ts: -------------------------------------------------------------------------------- 1 | // import { revalidatePath } from 'next/cache'; 2 | import { NextRequest } from 'next/server' 3 | import {customFetchBackend} from "@crosspublic/helpers/src/fetchObject/custom.fetch.backend"; 4 | 5 | export async function GET(request: NextRequest, context: {params: {token: string}}) { 6 | const cookie = request.cookies.get('auth')?.value; 7 | const {params: {token}} = context; 8 | 9 | if (cookie) { 10 | try { 11 | await customFetchBackend(cookie).post('/invite/add', { 12 | token 13 | }); 14 | } 15 | catch (err) {} 16 | 17 | // member logged in 18 | return Response.redirect(new URL('/success-add', request.url)); 19 | } 20 | 21 | return Response.redirect(new URL(`/auth/register/` + token, request.url)); 22 | } 23 | -------------------------------------------------------------------------------- /apps/panel/src/components/categories/categories.select.component.tsx: -------------------------------------------------------------------------------- 1 | import {Category} from "@prisma/client"; 2 | import {ChangeEvent, useCallback, useMemo} from "react"; 3 | import NiceModal from "@ebay/nice-modal-react"; 4 | import {CreateCategory} from "@crosspublic/panel/src/components/categories/create.category"; 5 | 6 | export const CategoriesSelectComponent = (props: {current: Category|undefined, categories: Category[], addToList: (categories: Category) => void, setCategory: (categories: Category|undefined) => void}) => { 7 | const value = useMemo(() => { 8 | return props?.current?.id; 9 | }, [props.current]); 10 | const setSelect = useCallback(async (e: ChangeEvent) => { 11 | if (!e.target.value) { 12 | props.setCategory(undefined); 13 | return; 14 | } 15 | if (e.target.value === 'new') { 16 | const createCat: Category = await NiceModal.show(CreateCategory, {title: 'Create a new category'}); 17 | props.addToList(createCat); 18 | props.setCategory(createCat); 19 | return false; 20 | } 21 | 22 | const category = props.categories.find(cat => cat.id === e.target.value); 23 | if (category) { 24 | props.setCategory(category); 25 | } 26 | }, []); 27 | return ( 28 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /apps/panel/src/components/icons/bell.icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { SVGProps } from "react" 3 | export const BellIcon = (props: SVGProps) => ( 4 | 11 | 15 | 16 | 17 | ); 18 | -------------------------------------------------------------------------------- /apps/panel/src/components/icons/billing.icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { SVGProps } from "react" 3 | const BillingIcon = (props: SVGProps) => ( 4 | 11 | 15 | 16 | ) 17 | export default BillingIcon; 18 | -------------------------------------------------------------------------------- /apps/panel/src/components/icons/delete.component.tsx: -------------------------------------------------------------------------------- 1 | import {SVGProps} from "react"; 2 | 3 | export const DeleteComponent = (props: SVGProps) => { 4 | return ( 5 | 19 | 20 | 21 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /apps/panel/src/components/icons/edit.component.tsx: -------------------------------------------------------------------------------- 1 | import {SVGProps} from "react"; 2 | 3 | export const EditComponent = (props: SVGProps) => { 4 | return ( 5 | 19 | 20 | 21 | 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /apps/panel/src/components/icons/exit.icon.tsx: -------------------------------------------------------------------------------- 1 | const ExitIcon = (props: any) => ( 2 | 10 | 17 | 18 | ) 19 | export default ExitIcon; 20 | -------------------------------------------------------------------------------- /apps/panel/src/components/icons/faq.icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { SVGProps } from "react" 3 | const FaqIcon = (props: SVGProps) => ( 4 | 11 | 15 | 16 | ) 17 | export default FaqIcon; 18 | -------------------------------------------------------------------------------- /apps/panel/src/components/icons/integrations.icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { SVGProps } from "react" 3 | const IntegrationsIcon = (props: SVGProps) => ( 4 | 12 | 13 | 14 | ) 15 | export default IntegrationsIcon; 16 | -------------------------------------------------------------------------------- /apps/panel/src/components/icons/logout.icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | const LogoutIcon = (props: any) => ( 3 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ) 22 | export default LogoutIcon; 23 | -------------------------------------------------------------------------------- /apps/panel/src/components/icons/move.component.tsx: -------------------------------------------------------------------------------- 1 | import {SVGProps} from "react"; 2 | 3 | export const MoveComponent = (props: SVGProps) => { 4 | return ( 5 | 15 | 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /apps/panel/src/components/icons/org.settings.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { SVGProps } from "react" 3 | const OrgSettingsIcon = (props: SVGProps) => ( 4 | 11 | 15 | 19 | 20 | ) 21 | export default OrgSettingsIcon; 22 | -------------------------------------------------------------------------------- /apps/panel/src/components/icons/small.arrow.down.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { SVGProps } from "react" 3 | export const SmallArrowDown = (props: SVGProps) => ( 4 | 11 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /apps/panel/src/components/icons/style.component.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { SVGProps } from "react" 3 | const StyleComponent = (props: SVGProps) => ( 4 | 11 | 16 | 17 | ) 18 | export default StyleComponent; 19 | -------------------------------------------------------------------------------- /apps/panel/src/components/icons/warning.icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | const WarningIcon = (props: any) => ( 3 | 10 | 15 | 16 | ) 17 | export default WarningIcon; 18 | -------------------------------------------------------------------------------- /apps/panel/src/components/jobs/job.component.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import {Jobs, Category} from '@prisma/client'; 4 | import {MessagesComponent} from "@crosspublic/panel/src/components/messages/messages.component"; 5 | import {useCallback, useState} from "react"; 6 | import {MessageWizard} from "@crosspublic/panel/src/components/messages/message.wizard"; 7 | import {wrapMeta} from "@crosspublic/panel/src/helpers/wrap.meta"; 8 | 9 | export const JobComponent = wrapMeta<{id: string, messages: Jobs, categories: Category[]}>(({messages, id, categories}) => { 10 | const [messagesContent, setMessagesContent] = useState(messages.messageContent as any[]); 11 | const changeRow = useCallback((message: any) => () => { 12 | setMessagesContent((messagesList: any) => { 13 | const index = messagesList.indexOf(message); 14 | return messagesList.map((p: any, curIndex: any) => ({ 15 | ...p, 16 | deleted: curIndex === index ? !p.deleted : p.deleted 17 | })); 18 | }); 19 | }, [messagesContent]); 20 | 21 | return ( 22 |
23 | 24 | 25 |
26 | ); 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /apps/panel/src/components/layout/layout.load.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import {useEffect} from "react"; 3 | import {useRouter} from "next/navigation"; 4 | 5 | const LayoutLoad = () => { 6 | const router = useRouter(); 7 | useEffect(() => { 8 | const auth = new URL(window.location.href); 9 | if (auth.searchParams.get('auth')) { 10 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 11 | // @ts-ignore 12 | auth.searchParams.delete('auth'); 13 | router.replace( 14 | auth.toString(), 15 | undefined, 16 | ); 17 | } 18 | }, [router]); 19 | 20 | return <>; 21 | } 22 | 23 | export default LayoutLoad; 24 | -------------------------------------------------------------------------------- /apps/panel/src/components/layout/toaster.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import {ToastContainer} from "react-toastify"; 4 | export const Toaster = () => { 5 | return ( 6 | 7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /apps/panel/src/components/loading/loading.component.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import {Oval} from "react-loader-spinner"; 4 | 5 | export const LoadingComponent = () => { 6 | return
7 | } 8 | -------------------------------------------------------------------------------- /apps/panel/src/components/payment/payment.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import {FC} from "react"; 4 | 5 | export const Payment: FC<{message?: string}> = (props) => { 6 | const {message} = props; 7 | return ( 8 | <>{message} 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /apps/panel/src/components/settings/api.component.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Input } from "../utils/input" 4 | import {FC, useCallback} from "react"; 5 | import {Button} from "@crosspublic/panel/src/components/utils/button"; 6 | import {toast} from "react-toastify"; 7 | 8 | export const ApiComponent: FC<{apiKey: string}> = (props) => { 9 | const {apiKey} = props; 10 | const copyToClipboard = useCallback(async () => { 11 | await navigator.clipboard.writeText(apiKey); 12 | toast.success('Copied to clipboard'); 13 | }, []); 14 | return ( 15 |
16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 |
24 | 29 |
30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /apps/panel/src/components/settings/domains/components/configured-section-placeholder.tsx: -------------------------------------------------------------------------------- 1 | const ConfiguredSectionPlaceholder = () => { 2 | return ( 3 |
4 |

5 | Loading Configuration... 6 |

7 |
8 | ) 9 | } 10 | 11 | export default ConfiguredSectionPlaceholder; 12 | -------------------------------------------------------------------------------- /apps/panel/src/components/settings/domains/components/domain-card-placeholder.tsx: -------------------------------------------------------------------------------- 1 | import LoadingDots from './loading-dots'; 2 | import ConfiguredSectionPlaceholder from './configured-section-placeholder'; 3 | 4 | const DomainCardPlaceholder = () => { 5 | return ( 6 |
7 |
8 |
9 |
10 | 16 | 20 |
21 |
22 | 23 |
24 | ) 25 | } 26 | 27 | export default DomainCardPlaceholder; 28 | -------------------------------------------------------------------------------- /apps/panel/src/components/settings/domains/components/loading-dots.module.css: -------------------------------------------------------------------------------- 1 | .loading { 2 | display: inline-flex; 3 | align-items: center; 4 | } 5 | 6 | .loading .spacer { 7 | margin-right: 2px; 8 | } 9 | 10 | .loading span { 11 | animation-name: blink; 12 | animation-duration: 1.4s; 13 | animation-iteration-count: infinite; 14 | animation-fill-mode: both; 15 | width: 5px; 16 | height: 5px; 17 | border-radius: 50%; 18 | background-color: #000; 19 | display: inline-block; 20 | margin: 0 1px; 21 | } 22 | 23 | .loading span:nth-of-type(2) { 24 | animation-delay: 0.2s; 25 | } 26 | 27 | .loading span:nth-of-type(3) { 28 | animation-delay: 0.4s; 29 | } 30 | 31 | @keyframes blink { 32 | 0% { 33 | opacity: 0.2; 34 | } 35 | 20% { 36 | opacity: 1; 37 | } 38 | 100% { 39 | opacity: 0.2; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /apps/panel/src/components/settings/domains/components/loading-dots.tsx: -------------------------------------------------------------------------------- 1 | import styles from './loading-dots.module.css' 2 | 3 | const LoadingDots = () => { 4 | return ( 5 | 6 | 7 | 8 | 9 | 10 | ) 11 | } 12 | 13 | export default LoadingDots; 14 | -------------------------------------------------------------------------------- /apps/panel/src/components/settings/settings.component.tsx: -------------------------------------------------------------------------------- 1 | import {Block} from "@crosspublic/panel/src/components/utils/block"; 2 | import Domains from "@crosspublic/panel/src/components/settings/domains/domains"; 3 | import {SubdomainComponent} from "@crosspublic/panel/src/components/settings/subdomain.component"; 4 | import {Metadata} from "next"; 5 | import {wrapMeta} from "@crosspublic/panel/src/helpers/wrap.meta"; 6 | import {ApiComponent} from "@crosspublic/panel/src/components/settings/api.component"; 7 | 8 | export const metadata: Metadata = { 9 | title: 'Settings', 10 | description: '', 11 | } 12 | 13 | export const SettingsComponent = wrapMeta<{subDomain: string, domains: any[], apiKey: string}>((props) => { 14 | const {subDomain, domains, apiKey} = props; 15 | return ( 16 | 17 | 18 | 19 | {!!apiKey && } 20 | 21 | ) 22 | }); 23 | -------------------------------------------------------------------------------- /apps/panel/src/components/user/user.component.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export const UserComponent = () => { 4 | return ( 5 |
6 |
7 | 14 | 19 | 20 | 21 |
22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /apps/panel/src/components/utils/block.tsx: -------------------------------------------------------------------------------- 1 | import {DetailedHTMLProps, FC, HTMLAttributes} from "react"; 2 | import {clsx} from "clsx"; 3 | 4 | export const Block: FC, HTMLDivElement>> = (props) => { 5 | return
6 | } 7 | -------------------------------------------------------------------------------- /apps/panel/src/components/utils/div.modal.tsx: -------------------------------------------------------------------------------- 1 | import {DetailedHTMLProps, FC, HTMLAttributes, useCallback} from "react"; 2 | import NiceModal from "@ebay/nice-modal-react"; 3 | 4 | export const DivModal: FC, HTMLDivElement> & {modal?: {component: FC, callback?:(val: any) => void, args?: any}}> = (props) => { 5 | const {modal, onClick, ...rest} = props; 6 | const newOnClick = useCallback(async (e: any) => { 7 | onClick && onClick(e); 8 | if (modal && modal.callback) { 9 | const callback = await NiceModal.show(modal.component, modal.args); 10 | modal.callback(callback); 11 | } 12 | }, [onClick, modal]); 13 | 14 | return ( 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /apps/panel/src/components/utils/input.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import {DetailedHTMLProps, FC, forwardRef, InputHTMLAttributes} from "react"; 4 | import {clsx} from "clsx"; 5 | import {FieldError} from "react-hook-form"; 6 | 7 | // @ts-ignore 8 | // eslint-disable-next-line react/display-name 9 | export const Input: FC, HTMLInputElement> & {label: string, error?: FieldError, topDivClass?: string}> = forwardRef((props, ref) => { 10 | const {className,label, error, required, topDivClass, ...rest} = props; 11 | return ( 12 |
13 | {!!label &&
{label}{required && *}
} 14 | 15 | {!!error &&
{error.message}
} 16 |
17 | ) 18 | }); 19 | -------------------------------------------------------------------------------- /apps/panel/src/components/utils/nice.modal.provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import NiceModal from "@ebay/nice-modal-react"; 4 | import {ReactNode} from "react"; 5 | 6 | export const NiceModalProvider = ({children}: {children: ReactNode}) => { 7 | return ( 8 | 9 | {children} 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /apps/panel/src/components/utils/tooltip.wrapper.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import {Tooltip} from "react-tooltip"; 4 | 5 | export const TooltipWrapper = () => { 6 | return ( 7 | 8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /apps/panel/src/components/utils/user.token.ts: -------------------------------------------------------------------------------- 1 | import { cookies, headers } from 'next/headers'; 2 | export const userToken = () => cookies().get('auth')?.value || headers().get('auth') || ''; 3 | -------------------------------------------------------------------------------- /apps/panel/src/helpers/delete.dialog.tsx: -------------------------------------------------------------------------------- 1 | import Swal from "sweetalert2"; 2 | 3 | export const deleteDialog = async (message: string, confirmButton?: string, finalMessage?: string) => { 4 | const fire = await Swal.fire({ 5 | title: 'Are you sure?', 6 | text: message, 7 | icon: 'warning', 8 | showCancelButton: true, 9 | confirmButtonText: confirmButton || 'Yes, delete it!', 10 | cancelButtonText: 'No, cancel!', 11 | }); 12 | 13 | if (!fire.isConfirmed) { 14 | throw new Error('User cancelled the dialog'); 15 | } 16 | 17 | return () => { 18 | Swal.fire( 19 | finalMessage || 'Deleted!', 20 | 'success' 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/panel/src/helpers/fetcher.tsx: -------------------------------------------------------------------------------- 1 | import {customFetch} from "@crosspublic/panel/src/helpers/fetch.context"; 2 | 3 | const fetcher = async (...args: any[]) => { 4 | // @ts-ignore 5 | const {data} = await customFetch.get(...args) 6 | return data; 7 | } 8 | 9 | export default fetcher; 10 | -------------------------------------------------------------------------------- /apps/panel/src/helpers/revalidate.domain.ts: -------------------------------------------------------------------------------- 1 | export const revalidateDomain = async () => { 2 | await fetch('/api/revalidate'); 3 | } 4 | -------------------------------------------------------------------------------- /apps/panel/src/helpers/title.helper.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import {createContext, FC, ReactNode, useContext, useEffect, useState} from "react"; 4 | 5 | // eslint-disable-next-line @typescript-eslint/no-empty-function 6 | const TitleContext = createContext({title: '', changeTitle: (title: string) => {}}); 7 | 8 | export const TitleProvider: FC<{children: ReactNode}> = ({ children}) => { 9 | const [titleState, changeTitle] = useState(''); 10 | return ( 11 | 12 | {children} 13 | 14 | ) 15 | } 16 | 17 | export const Title = () => { 18 | const {title} = useContext(TitleContext); 19 | return ( 20 | <>{title} 21 | ) 22 | } 23 | 24 | export const ChangeTitle: FC<{newTitle: string}> = ({newTitle}) => { 25 | const {changeTitle} = useContext(TitleContext); 26 | useEffect(() => { 27 | changeTitle(newTitle); 28 | return () => { 29 | changeTitle(''); 30 | } 31 | }, []); 32 | return <>; 33 | } 34 | -------------------------------------------------------------------------------- /apps/panel/src/helpers/user.context.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import {createContext, ReactNode, useContext} from "react"; 4 | import {UserInterface} from "@crosspublic/helpers/src/user/user.interface"; 5 | 6 | export const UserHigherContext = createContext<{user: UserInterface} & {pricing: boolean}>({ 7 | pricing: false, 8 | user: { 9 | email: '', 10 | id: '', 11 | name: '', 12 | internalId: '', 13 | organization: { 14 | organizationId: '', 15 | userId: '', 16 | role: 'USER', 17 | }, 18 | }}); 19 | 20 | export const UserContext = (props: {children: ReactNode, user: UserInterface, pricing: boolean}) => { 21 | return ( 22 | 23 | {props.children} 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /apps/panel/src/helpers/wrap.meta.tsx: -------------------------------------------------------------------------------- 1 | import {JSX, ComponentType} from "react"; 2 | import {Metadata} from "next"; 3 | import {ChangeTitle} from "@crosspublic/panel/src/helpers/title.helper"; 4 | 5 | interface Title { 6 | metadata: Metadata; 7 | } 8 | // eslint-disable-next-line react/display-name 9 | export const wrapMeta = (WrapperComponent: ComponentType) => { 10 | // eslint-disable-next-line react/display-name 11 | return (props: T & Title): JSX.Element => { 12 | 13 | return ( 14 | <> 15 | 16 |
17 | 18 |
19 | 20 | ); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /apps/panel/src/pages/api/upload.ts: -------------------------------------------------------------------------------- 1 | import { put } from "@vercel/blob"; 2 | import { NextResponse } from "next/server"; 3 | import {NextApiRequest} from "next"; 4 | 5 | export const runtime = "edge"; 6 | 7 | export default async function handler(req: NextApiRequest) { 8 | if (!process.env.BLOB_READ_WRITE_TOKEN) { 9 | return new Response( 10 | "Missing BLOB_READ_WRITE_TOKEN. Don't forget to add that to your .env file.", 11 | { 12 | status: 401, 13 | }, 14 | ); 15 | } 16 | 17 | const file = req.body || ""; 18 | // @ts-ignore 19 | const filename = req?.headers?.get("x-vercel-filename") || "file.txt"; 20 | 21 | // @ts-ignore 22 | const contentType = req?.headers?.get("content-type") || "text/plain"; 23 | const fileType = `.${contentType.split("/")[1]}`; 24 | 25 | // construct final filename based on content-type if not provided 26 | const finalName = filename.includes(fileType) 27 | ? filename 28 | : `${filename}${fileType}`; 29 | const blob = await put(finalName, file, { 30 | contentType, 31 | access: "public", 32 | }); 33 | 34 | return NextResponse.json(blob); 35 | } 36 | -------------------------------------------------------------------------------- /apps/panel/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const { createGlobPatternsForDependencies } = require('@nx/react/tailwind'); 2 | const { join } = require('path'); 3 | 4 | module.exports = { 5 | darkMode: 'class', 6 | mode: 'jit', 7 | content: [ 8 | join( 9 | __dirname, 10 | '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}' 11 | ), 12 | ...createGlobPatternsForDependencies(__dirname), 13 | ], 14 | purge: [ 15 | join(__dirname, '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}') 16 | ], 17 | theme: { 18 | extend: { 19 | colors: { 20 | primary: "#3B1D61", 21 | secondary: "#2A1544", 22 | line: '#CBCEDC', 23 | gray: '#8A919C', 24 | pink: '#AE4DDC' 25 | }, 26 | backgroundImage: { 27 | auth: 'url(/bg.svg)', 28 | menu: 'linear-gradient(270deg, rgba(174, 77, 220, 0.02) 0%, rgba(174, 77, 220, 0.05) 37.65%, rgba(174, 77, 220, 0.10) 100%)', 29 | btn: 'linear-gradient(225deg, #AE4DDC -5.28%, #B85AF2 -5.27%, #9142E0 72.52%)', 30 | btnHover: 'linear-gradient(225deg, #863ba9 -5.28%, #B85AF2 -5.27%, #7a39bc 72.52%)', 31 | }, 32 | animation: { 33 | 'fade-up': 'fadeInUp 1s ease forwards', 34 | 'fade-in': 'fadeIn 1s ease forwards', 35 | }, 36 | fontSize: { 37 | main: '18px' 38 | }, 39 | fontWeight: { 40 | main: '600' 41 | }, 42 | borderRadius: { 43 | container: '10px' 44 | } 45 | } 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /apps/panel/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "resolveJsonModule": true, 12 | "skipDefaultLibCheck": true, 13 | "isolatedModules": true, 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "types": [ 21 | "jest", 22 | "node" 23 | ] 24 | }, 25 | "include": [ 26 | "**/*.ts", 27 | "**/*.tsx", 28 | "**/*.js", 29 | "**/*.jsx", 30 | "next-env.d.ts", 31 | "../../apps/panel/.next/types/**/*.ts" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /apps/panel/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"], 7 | "jsx": "react" 8 | }, 9 | "include": [ 10 | "jest.config.ts", 11 | "src/**/*.test.ts", 12 | "src/**/*.spec.ts", 13 | "src/**/*.test.tsx", 14 | "src/**/*.spec.tsx", 15 | "src/**/*.test.js", 16 | "src/**/*.spec.js", 17 | "src/**/*.test.jsx", 18 | "src/**/*.spec.jsx", 19 | "src/**/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /apps/tenants/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@nx/react-typescript", 4 | "next", 5 | "next/core-web-vitals", 6 | "../../.eslintrc.json" 7 | ], 8 | "ignorePatterns": ["!**/*", ".next/**/*"], 9 | "rules": { 10 | "@nx/enforce-module-boundaries": "off", 11 | "@typescript-eslint/no-non-null-asserted-optional-chain": "off", 12 | "@typescript-eslint/no-explicit-any": "off", 13 | "react/no-children-prop" : "off", 14 | "@typescript-eslint/ban-ts-comment": "off", 15 | "@typescript-eslint/no-var-requires": "off", 16 | "no-empty": "off" 17 | }, 18 | "overrides": [ 19 | { 20 | "files": ["*.*"], 21 | "rules": { 22 | "@next/next/no-html-link-for-pages": "off" 23 | } 24 | }, 25 | { 26 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 27 | "rules": { 28 | "@next/next/no-html-link-for-pages": ["error", "apps/tenants/pages"] 29 | } 30 | }, 31 | { 32 | "files": ["*.ts", "*.tsx"], 33 | "rules": {} 34 | }, 35 | { 36 | "files": ["*.js", "*.jsx"], 37 | "rules": {} 38 | }, 39 | { 40 | "files": ["*.spec.ts", "*.spec.tsx", "*.spec.js", "*.spec.jsx"], 41 | "env": { 42 | "jest": true 43 | } 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /apps/tenants/index.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module '*.svg' { 3 | const content: any; 4 | export const ReactComponent: any; 5 | export default content; 6 | } 7 | -------------------------------------------------------------------------------- /apps/tenants/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'tenants', 4 | preset: '../../jest.preset.js', 5 | transform: { 6 | '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nx/react/plugins/jest', 7 | '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nx/next/babel'] }], 8 | }, 9 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 10 | coverageDirectory: '../../coverage/apps/tenants', 11 | }; 12 | -------------------------------------------------------------------------------- /apps/tenants/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/tenants/next.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const { composePlugins, withNx } = require('@nx/next'); 5 | 6 | /** 7 | * @type {import('@nx/next/plugins/with-nx').WithNxOptions} 8 | **/ 9 | const nextConfig = { 10 | nx: { 11 | // Set this to true if you would like to use SVGR 12 | // See: https://github.com/gregberge/svgr 13 | svgr: false, 14 | }, 15 | env: { 16 | FRONTEND_URL: process.env.FRONTEND_URL || '', 17 | }, 18 | transpilePackages: ['react-syntax-highlighter', 'geist'], 19 | typescript: { 20 | ignoreBuildErrors: true, 21 | }, 22 | }; 23 | 24 | const plugins = [ 25 | // Add more Next.js plugins to this list if needed. 26 | withNx, 27 | ]; 28 | 29 | module.exports = composePlugins(...plugins)(nextConfig); 30 | -------------------------------------------------------------------------------- /apps/tenants/postcss.config.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: { 6 | config: join(__dirname, 'tailwind.config.js'), 7 | }, 8 | autoprefixer: {}, 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /apps/tenants/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tenants", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "apps/tenants", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nx/next:build", 9 | "outputs": ["{options.outputPath}"], 10 | "defaultConfiguration": "production", 11 | "options": { 12 | "outputPath": "dist/apps/tenants", 13 | "postcssConfig": "apps/tenants/postcss.config.js" 14 | }, 15 | "configurations": { 16 | "development": { 17 | "outputPath": "apps/tenants" 18 | }, 19 | "production": {} 20 | } 21 | }, 22 | "serve": { 23 | "executor": "@nx/next:server", 24 | "defaultConfiguration": "development", 25 | "options": { 26 | "buildTarget": "tenants:build", 27 | "dev": true, 28 | "port": 4202 29 | }, 30 | "configurations": { 31 | "development": { 32 | "buildTarget": "tenants:build:development", 33 | "dev": true 34 | }, 35 | "production": { 36 | "buildTarget": "tenants:build:production", 37 | "dev": false 38 | } 39 | } 40 | }, 41 | "export": { 42 | "executor": "@nx/next:export", 43 | "options": { 44 | "buildTarget": "tenants:build:production" 45 | } 46 | }, 47 | "test": { 48 | "executor": "@nx/jest:jest", 49 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 50 | "options": { 51 | "jestConfig": "apps/tenants/jest.config.ts" 52 | } 53 | }, 54 | "lint": { 55 | "executor": "@nx/eslint:lint", 56 | "outputs": ["{options.outputFile}"], 57 | "options": { 58 | "lintFilePatterns": ["apps/tenants/**/*.{ts,tsx,js,jsx}"] 59 | } 60 | } 61 | }, 62 | "tags": [] 63 | } 64 | -------------------------------------------------------------------------------- /apps/tenants/public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/tenants/public/.gitkeep -------------------------------------------------------------------------------- /apps/tenants/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/tenants/public/favicon.ico -------------------------------------------------------------------------------- /apps/tenants/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /apps/tenants/public/logobot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/tenants/public/logobot.png -------------------------------------------------------------------------------- /apps/tenants/public/usericon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitroomhq/crosspublic/f3b626e3315c465dd3705f31b2f013259b19431e/apps/tenants/public/usericon.png -------------------------------------------------------------------------------- /apps/tenants/src/app/(customers)/[customer]/page.tsx: -------------------------------------------------------------------------------- 1 | import {publicRequestFetch} from "@crosspublic/tenants/src/helpers/get.api.key"; 2 | import {Category} from "@prisma/client"; 3 | import {textToMarkdown} from "@crosspublic/tenants/src/helpers/text.to.markdown"; 4 | import Link from "next/link"; 5 | import {Suspense} from "react"; 6 | import { Metadata, ResolvingMetadata } from "next"; 7 | 8 | export const dynamic = 'force-static'; 9 | 10 | type Props = { 11 | params: { customer: string } 12 | } 13 | 14 | export async function generateMetadata( 15 | { params }: Props, 16 | parent: ResolvingMetadata 17 | ): Promise { 18 | // read route params 19 | const {customer} = params; 20 | const {name} = await publicRequestFetch(customer); 21 | 22 | return { 23 | title: name + ' FAQ' 24 | } 25 | } 26 | 27 | export default async function Page({params: {customer}} : {params: {customer: string}}) { 28 | const {tags, request} = await publicRequestFetch(customer); 29 | const {data} = await request.get(`/public/categories?c=${customer}`, {cache: 'force-cache', next: {tags: [tags]}}); 30 | 31 | return ( 32 | 33 |
34 | {data.map((category: Category & {slug: string}) => ( 35 | 38 |
39 | {category.name} 40 |
41 |
42 | 43 | ))} 44 |
45 | 46 | ) 47 | } 48 | 49 | -------------------------------------------------------------------------------- /apps/tenants/src/app/api/revalidate/route.ts: -------------------------------------------------------------------------------- 1 | // import { revalidatePath } from 'next/cache'; 2 | import { NextRequest } from 'next/server' 3 | import {revalidateTag} from "next/cache"; 4 | 5 | export async function POST(request: NextRequest) { 6 | const body = await request.json(); 7 | if (request.headers.get('serverkey') === process.env.BACKEND_TOKEN_PROTECTOR && body.name) { 8 | revalidateTag(body.name); 9 | } 10 | return Response.json({ revalidated: true, now: Date.now() }); 11 | } 12 | -------------------------------------------------------------------------------- /apps/tenants/src/components/claim/claim.this.page.component.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useRouter } from 'next/navigation'; 3 | import { Button } from '@crosspublic/tenants/src/components/utils/button'; 4 | 5 | export const ClaimThisPageComponent = () => { 6 | const router = useRouter(); 7 | const goToLogin = () => { 8 | router.push(process.env.FRONTEND_URL + '/login'); 9 | }; 10 | return ( 11 |
12 |

Hi

13 |

14 | This page can be yours, claim it. 15 |

16 |
17 | 18 |
19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /apps/tenants/src/components/utils/after.highlight.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import {FC, useEffect} from "react"; 4 | import hljs from "highlight.js"; 5 | 6 | export const AfterHighlight: FC<{id: string}> = (props) => { 7 | const {id} = props; 8 | useEffect(() => { 9 | const allCodes = Array.from(document.querySelectorAll('#' + id + ' code')); 10 | for (const code of allCodes) { 11 | // @ts-ignore 12 | hljs.highlightElement(code); 13 | } 14 | }, []); 15 | 16 | return <>; 17 | } 18 | -------------------------------------------------------------------------------- /apps/tenants/src/helpers/get.api.key.tsx: -------------------------------------------------------------------------------- 1 | import {customFetchBackend, CustomFetchBackendInterface} from "@crosspublic/helpers/src/fetchObject/custom.fetch.backend"; 2 | import * as process from "process"; 3 | 4 | export const publicRequestFetch = async (domain: string): Promise<{redirect?: false | string, id: string, tags: string, name: string, request: CustomFetchBackendInterface}> => { 5 | const getDomainSubdomain = domain.indexOf('.') ? {subdomain: domain.split('.')[0].replace('www', '')} : {domain}; 6 | const {data: {apiKey, id, name}} = await customFetchBackend().get( 7 | `/public/organization?${new URLSearchParams(getDomainSubdomain as never).toString()}`, 8 | {cache: 'force-cache', next: {tags: [domain]}, headers: {serverkey: process.env.BACKEND_TOKEN_PROTECTOR!}} 9 | ); 10 | 11 | if (!apiKey) { 12 | return {tags: false, request: false} as any; 13 | } 14 | 15 | const redirect = apiKey.indexOf('http') > -1 ? apiKey : false; 16 | 17 | return {redirect, id, name, tags: getDomainSubdomain?.domain || getDomainSubdomain?.subdomain || '', request: customFetchBackend(undefined, {apikey: apiKey, serverkey: process.env.BACKEND_TOKEN_PROTECTOR, tags: [getDomainSubdomain?.domain || getDomainSubdomain?.subdomain || '']})}; 18 | } 19 | -------------------------------------------------------------------------------- /apps/tenants/src/helpers/text.to.markdown.ts: -------------------------------------------------------------------------------- 1 | import showdown from 'showdown'; 2 | const converter = new showdown.Converter(); 3 | 4 | export const textToMarkdown = (text: string) => { 5 | return converter.makeHtml(text); 6 | } 7 | -------------------------------------------------------------------------------- /apps/tenants/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server' 2 | import type { NextRequest } from 'next/server' 3 | 4 | export const getOrg = (url: string) => { 5 | const frontEndUrl = new URL(process.env.TENANTS_URL!).host; 6 | if (url.indexOf(frontEndUrl) > -1) { 7 | const host = new URL(url).hostname; 8 | return host?.split('.')?.[0] || 'testserver'; 9 | } 10 | 11 | return new URL(url).host.split(':')[0]; 12 | } 13 | 14 | // This function can be marked `async` if using `await` inside 15 | export async function middleware(request: NextRequest) { 16 | const nextUrl = request.nextUrl; 17 | const protocol = nextUrl.protocol; 18 | const searchParams = nextUrl.searchParams.toString(); 19 | const path = `${nextUrl.pathname}${ 20 | searchParams.length > 0 ? `?${searchParams}` : "" 21 | }`; 22 | 23 | // The real host address 24 | const realHost = `${protocol}//` + (request.headers.get('x-forwarded-host') || request.headers.get('host')!); 25 | 26 | const getCustomer = getOrg(realHost); 27 | return NextResponse.rewrite(new URL(`/${getCustomer}${path === "/" ? "" : path}`, request.url)); 28 | } 29 | 30 | // See "Matching Paths" below to learn more 31 | export const config = { 32 | matcher: "/((?!api/|_next/|_static/|_vercel|[\\w-]+\\.\\w+).*)", 33 | } 34 | 35 | -------------------------------------------------------------------------------- /apps/tenants/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const { createGlobPatternsForDependencies } = require('@nx/react/tailwind'); 2 | const { join } = require('path'); 3 | 4 | module.exports = { 5 | darkMode: 'class', 6 | mode: 'jit', 7 | content: [ 8 | join( 9 | __dirname, 10 | '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}' 11 | ), 12 | ...createGlobPatternsForDependencies(__dirname), 13 | ], 14 | purge: [ 15 | join(__dirname, '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}') 16 | ], 17 | theme: { 18 | extend: { 19 | colors: { 20 | primary: "#3B1D61", 21 | secondary: "#2A1544", 22 | line: '#CBCEDC', 23 | gray: '#8A919C', 24 | pink: '#AE4DDC' 25 | }, 26 | backgroundImage: { 27 | menu: 'linear-gradient(270deg, rgba(174, 77, 220, 0.02) 0%, rgba(174, 77, 220, 0.05) 37.65%, rgba(174, 77, 220, 0.10) 100%)', 28 | btn: 'linear-gradient(225deg, #AE4DDC -5.28%, #B85AF2 -5.27%, #9142E0 72.52%)', 29 | btnHover: 'linear-gradient(225deg, #863ba9 -5.28%, #B85AF2 -5.27%, #7a39bc 72.52%)', 30 | }, 31 | animation: { 32 | 'fade-up': 'fadeInUp 1s ease forwards', 33 | 'fade-in': 'fadeIn 1s ease forwards', 34 | }, 35 | fontSize: { 36 | main: '18px' 37 | }, 38 | fontWeight: { 39 | main: '600' 40 | }, 41 | borderRadius: { 42 | container: '10px' 43 | } 44 | } 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /apps/tenants/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "resolveJsonModule": true, 12 | "skipDefaultLibCheck": true, 13 | "isolatedModules": true, 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "types": [ 21 | "jest", 22 | "node" 23 | ] 24 | }, 25 | "include": [ 26 | "**/*.ts", 27 | "**/*.tsx", 28 | "**/*.js", 29 | "**/*.jsx", 30 | "next-env.d.ts", 31 | "../../apps/tenants/.next/types/**/*.ts" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /apps/tenants/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"], 7 | "jsx": "react" 8 | }, 9 | "include": [ 10 | "jest.config.ts", 11 | "src/**/*.test.ts", 12 | "src/**/*.spec.ts", 13 | "src/**/*.test.tsx", 14 | "src/**/*.spec.tsx", 15 | "src/**/*.test.js", 16 | "src/**/*.spec.js", 17 | "src/**/*.test.jsx", 18 | "src/**/*.spec.jsx", 19 | "src/**/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { getJestProjects } from '@nx/jest'; 2 | 3 | export default { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nx/jest/preset').default; 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /libraries/database/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "rules": { 5 | "@nx/enforce-module-boundaries": "off" 6 | }, 7 | "overrides": [ 8 | { 9 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 10 | "rules": {} 11 | }, 12 | { 13 | "files": ["*.ts", "*.tsx"], 14 | "rules": {} 15 | }, 16 | { 17 | "files": ["*.js", "*.jsx"], 18 | "rules": {} 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /libraries/database/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | # Keep environment variables out of version control 3 | .env 4 | -------------------------------------------------------------------------------- /libraries/database/README.md: -------------------------------------------------------------------------------- 1 | # database 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test database` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libraries/database/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'database', 4 | preset: '../../jest.preset.js', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 8 | }, 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageDirectory: '../../coverage/libraries/database', 11 | }; 12 | -------------------------------------------------------------------------------- /libraries/database/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "database", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "libraries/database/src", 5 | "projectType": "library", 6 | "targets": { 7 | "lint": { 8 | "executor": "@nx/eslint:lint", 9 | "outputs": ["{options.outputFile}"], 10 | "options": { 11 | "lintFilePatterns": ["libraries/database/**/*.ts"] 12 | } 13 | }, 14 | "test": { 15 | "executor": "@nx/jest:jest", 16 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 17 | "options": { 18 | "jestConfig": "libraries/database/jest.config.ts" 19 | } 20 | } 21 | }, 22 | "tags": [] 23 | } 24 | -------------------------------------------------------------------------------- /libraries/database/src/categories/category.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import {CategoryService} from "@crosspublic/database/src/categories/category.service"; 3 | import {CategoryRepository} from "@crosspublic/database/src/categories/category.repository"; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [], 8 | providers: [CategoryService, CategoryRepository], 9 | get exports() { 10 | return this.providers; 11 | } 12 | }) 13 | export class CategoryModule {} 14 | -------------------------------------------------------------------------------- /libraries/database/src/domains/domain.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import {DomainRepository} from "@crosspublic/database/src/domains/domain.repository"; 3 | import {DomainService} from "@crosspublic/database/src/domains/domain.service"; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [], 8 | providers: [DomainRepository, DomainService], 9 | get exports() { 10 | return this.providers; 11 | } 12 | }) 13 | export class DomainModule {} 14 | -------------------------------------------------------------------------------- /libraries/database/src/domains/domain.repository.ts: -------------------------------------------------------------------------------- 1 | import {PrismaRepository} from "../../src/prisma.service"; 2 | import {Injectable} from "@nestjs/common"; 3 | 4 | @Injectable() 5 | export class DomainRepository { 6 | constructor( 7 | private readonly _prisma: PrismaRepository<'domains'>, 8 | ) { 9 | } 10 | 11 | totalDomainsByOrganizationId(organizationId: string) { 12 | return this._prisma.model.domains.count({ 13 | where: { 14 | organizationId, 15 | deletedAt: null, 16 | }, 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libraries/database/src/domains/domain.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from "@nestjs/common"; 2 | import {DomainRepository} from "@crosspublic/database/src/domains/domain.repository"; 3 | 4 | @Injectable() 5 | export class DomainService { 6 | constructor( 7 | private readonly _domainRepository: DomainRepository 8 | ) {} 9 | 10 | totalDomainsByOrganizationId(organizationId: string) { 11 | return this._domainRepository.totalDomainsByOrganizationId(organizationId); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /libraries/database/src/faq/faq.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import {FaqService} from "@crosspublic/database/src/faq/faq.service"; 3 | import {FaqRepository} from "@crosspublic/database/src/faq/faq.repository"; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [], 8 | providers: [FaqService, FaqRepository], 9 | get exports() { 10 | return this.providers; 11 | } 12 | }) 13 | export class FaqModule {} 14 | -------------------------------------------------------------------------------- /libraries/database/src/integrations/integrations.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import {IntegrationsService} from "@crosspublic/database/src/integrations/integrations.service"; 3 | import {IntegrationsRepository} from "@crosspublic/database/src/integrations/integrations.repository"; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [], 8 | providers: [IntegrationsService, IntegrationsRepository], 9 | get exports() { 10 | return this.providers; 11 | } 12 | }) 13 | export class IntegrationsModule {} 14 | -------------------------------------------------------------------------------- /libraries/database/src/jobs/jobs.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import {JobsService} from "@crosspublic/database/src/jobs/jobs.service"; 3 | import {JobsRepository} from "@crosspublic/database/src/jobs/jobs.repository"; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [], 8 | providers: [JobsService, JobsRepository], 9 | get exports() { 10 | return this.providers; 11 | } 12 | }) 13 | export class JobsModule {} 14 | -------------------------------------------------------------------------------- /libraries/database/src/jobs/jobs.repository.ts: -------------------------------------------------------------------------------- 1 | import {PrismaRepository} from "../../src/prisma.service"; 2 | import {Injectable} from "@nestjs/common"; 3 | 4 | @Injectable() 5 | export class JobsRepository { 6 | constructor( 7 | private readonly _prisma: PrismaRepository<'jobs'>, 8 | ) { 9 | } 10 | insertJob(organizationId: string, referenceId: string, messages: object) { 11 | return this._prisma.model.jobs.create({ 12 | data: { 13 | messageContent: messages, 14 | referenceId, 15 | organizationId, 16 | state: 'PENDING' 17 | } 18 | }) 19 | } 20 | 21 | getJobById(organizationId: string, id: string) { 22 | return this._prisma.model.jobs.findFirst({ 23 | where: { 24 | organizationId, 25 | id 26 | } 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /libraries/database/src/jobs/jobs.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from "@nestjs/common"; 2 | import {JobsRepository} from "@crosspublic/database/src/jobs/jobs.repository"; 3 | 4 | @Injectable() 5 | export class JobsService { 6 | constructor( 7 | private readonly _jobsRepository: JobsRepository 8 | ) {} 9 | insertJob(organizationId: string, referenceId: string, messages: object) { 10 | return this._jobsRepository.insertJob(organizationId, referenceId, messages); 11 | } 12 | 13 | getJobById(organizationId: string, id: string) { 14 | return this._jobsRepository.getJobById(organizationId, id); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /libraries/database/src/organization/organization.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import {OrganizationService} from "@crosspublic/database/src/organization/organization.service"; 3 | import {OrganizationRepository} from "@crosspublic/database/src/organization/organization.repository"; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [], 8 | providers: [OrganizationService, OrganizationRepository], 9 | get exports() { 10 | return this.providers; 11 | } 12 | }) 13 | export class OrganizationModule {} 14 | -------------------------------------------------------------------------------- /libraries/database/src/prisma.module.ts: -------------------------------------------------------------------------------- 1 | import {Global, Module} from '@nestjs/common'; 2 | import {OrganizationModule} from "@crosspublic/database/src/organization/organization.module"; 3 | import {PrismaRepository, PrismaService} from "@crosspublic/database/src/prisma.service"; 4 | import {UserModule} from "@crosspublic/database/src/users/user.module"; 5 | import {JobsModule} from "@crosspublic/database/src/jobs/jobs.module"; 6 | import {SubscriptionModule} from "@crosspublic/database/src/subscription/subscription.module"; 7 | import {CategoryModule} from "@crosspublic/database/src/categories/category.module"; 8 | import {FaqModule} from "@crosspublic/database/src/faq/faq.module"; 9 | import {DomainModule} from "@crosspublic/database/src/domains/domain.module"; 10 | import {SettingsModule} from "@crosspublic/database/src/settings/settings.module"; 11 | import {IntegrationsModule} from "@crosspublic/database/src/integrations/integrations.module"; 12 | 13 | @Global() 14 | @Module({ 15 | imports: [ 16 | OrganizationModule, 17 | UserModule, 18 | SubscriptionModule, 19 | FaqModule, 20 | DomainModule, 21 | CategoryModule, 22 | SettingsModule, 23 | IntegrationsModule, 24 | JobsModule 25 | ], 26 | controllers: [], 27 | providers: [PrismaService, PrismaRepository], 28 | get exports() { 29 | return [...this.imports, ...this.providers]; 30 | } 31 | }) 32 | export class PrismaModule {} 33 | -------------------------------------------------------------------------------- /libraries/database/src/prisma.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnModuleInit } from '@nestjs/common'; 2 | import { PrismaClient } from '@prisma/client'; 3 | 4 | @Injectable() 5 | export class PrismaService extends PrismaClient implements OnModuleInit { 6 | constructor() { 7 | super({ 8 | log: [] 9 | }); 10 | } 11 | async onModuleInit() { 12 | await this['$connect'](); 13 | } 14 | } 15 | 16 | 17 | @Injectable() 18 | export class PrismaRepository { 19 | public model: Pick; 20 | constructor(private _prismaService: PrismaService) { 21 | this.model = this._prismaService; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /libraries/database/src/settings/settings.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import {SettingsService} from "@crosspublic/database/src/settings/settings.service"; 3 | import {SettingsRepository} from "@crosspublic/database/src/settings/settings.repository"; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [], 8 | providers: [SettingsService, SettingsRepository], 9 | get exports() { 10 | return this.providers; 11 | } 12 | }) 13 | export class SettingsModule {} 14 | -------------------------------------------------------------------------------- /libraries/database/src/subscription/subscription.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import {SubscriptionService} from "@crosspublic/database/src/subscription/subscription.service"; 3 | import {SubscriptionRepository} from "@crosspublic/database/src/subscription/subscription.repository"; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [], 8 | providers: [SubscriptionService, SubscriptionRepository], 9 | get exports() { 10 | return this.providers; 11 | } 12 | }) 13 | export class SubscriptionModule {} 14 | -------------------------------------------------------------------------------- /libraries/database/src/users/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import {UserRepository} from "@crosspublic/database/src/users/user.repository"; 3 | import {UserService} from "@crosspublic/database/src/users/user.service"; 4 | 5 | @Module({ 6 | imports: [], 7 | controllers: [], 8 | providers: [UserRepository, UserService], 9 | get exports() { 10 | return this.providers; 11 | } 12 | }) 13 | export class UserModule {} 14 | -------------------------------------------------------------------------------- /libraries/database/src/users/user.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from "@nestjs/common"; 2 | import {UserRepository} from "@crosspublic/database/src/users/user.repository"; 3 | 4 | @Injectable() 5 | export class UserService { 6 | constructor( 7 | private readonly _userRepository: UserRepository 8 | ) {} 9 | 10 | getUserByEmail(email: string) { 11 | return this._userRepository.getUserByEmail(email); 12 | } 13 | 14 | totalUsersByOrganizationId(orgId: string) { 15 | return this._userRepository.totalUsersByOrganizationId(orgId); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /libraries/database/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "types": ["node"], 6 | "emitDecoratorMetadata": true, 7 | "target": "es2021", 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitOverride": true, 11 | "noPropertyAccessFromIndexSignature": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "experimentalDecorators": true, 15 | }, 16 | "files": [], 17 | "include": [], 18 | "references": [ 19 | { 20 | "path": "./tsconfig.lib.json" 21 | }, 22 | { 23 | "path": "./tsconfig.spec.json" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /libraries/database/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"], 7 | "target": "es2021", 8 | "strictNullChecks": true, 9 | "noImplicitAny": true, 10 | "strictBindCallApply": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noFallthroughCasesInSwitch": true 13 | }, 14 | "include": ["src/**/*.ts"], 15 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /libraries/database/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /libraries/helpers/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "rules": { 5 | "@nx/enforce-module-boundaries": "off", 6 | "@typescript-eslint/no-explicit-any": "off", 7 | "@typescript-eslint/no-non-null-asserted-optional-chain": "off" 8 | }, 9 | "overrides": [ 10 | { 11 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 12 | "rules": {} 13 | }, 14 | { 15 | "files": ["*.ts", "*.tsx"], 16 | "rules": {} 17 | }, 18 | { 19 | "files": ["*.js", "*.jsx"], 20 | "rules": {} 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /libraries/helpers/README.md: -------------------------------------------------------------------------------- 1 | # helpers 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test helpers` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libraries/helpers/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'helpers', 4 | preset: '../../jest.preset.js', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 8 | }, 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageDirectory: '../../coverage/libraries/helpers', 11 | }; 12 | -------------------------------------------------------------------------------- /libraries/helpers/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "helpers", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "libraries/helpers/src", 5 | "projectType": "library", 6 | "targets": { 7 | "lint": { 8 | "executor": "@nx/eslint:lint", 9 | "outputs": ["{options.outputFile}"], 10 | "options": { 11 | "lintFilePatterns": ["libraries/helpers/**/*.ts"] 12 | } 13 | }, 14 | "test": { 15 | "executor": "@nx/jest:jest", 16 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 17 | "options": { 18 | "jestConfig": "libraries/helpers/jest.config.ts" 19 | } 20 | } 21 | }, 22 | "tags": [] 23 | } 24 | -------------------------------------------------------------------------------- /libraries/helpers/src/algolia/algolia.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from "@nestjs/common"; 2 | import algoliasearch from 'algoliasearch'; 3 | 4 | // Connect and authenticate with your Algolia app 5 | const client = algoliasearch(process.env.NEXT_PUBLIC_ALGOLIA_APP_ID!, process.env.ALGOLIA_ADMIN_API_KEY!); 6 | 7 | @Injectable() 8 | export class AlgoliaService { 9 | async insertRecord(orgId: string, object: T) { 10 | return client.initIndex(orgId).saveObject(object).wait(); 11 | } 12 | 13 | async deleteRecord(orgId: string, objectId: string) { 14 | return client.initIndex(orgId).deleteObject(objectId).wait(); 15 | } 16 | 17 | async updateRecord(orgId: string, object: T) { 18 | return client.initIndex(orgId).partialUpdateObject(object, { 19 | createIfNotExists: true 20 | }).wait(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /libraries/helpers/src/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import {sign, verify} from 'jsonwebtoken'; 2 | export class AuthService { 3 | static signJWT (value: object) { 4 | return sign(value, process.env.JWT_SECRET!); 5 | } 6 | 7 | static verifyJWT (token: string) { 8 | return verify(token, process.env.JWT_SECRET!); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libraries/helpers/src/components/code.block.component.tsx: -------------------------------------------------------------------------------- 1 | import { NodeViewContent, NodeViewWrapper } from '@tiptap/react' 2 | import languages from 'highlight.js'; 3 | 4 | export default ({node, updateAttributes }) => { 5 | const {attrs: { language: defaultLanguage }} = node; 6 | return ( 7 | 8 | 33 |
34 |         
35 |       
36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /libraries/helpers/src/makeid/make.id.ts: -------------------------------------------------------------------------------- 1 | export const makeId = (length: number) => { 2 | let text = ''; 3 | const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 4 | 5 | for (let i = 0; i < length; i += 1) { 6 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 7 | } 8 | return text; 9 | }; 10 | -------------------------------------------------------------------------------- /libraries/helpers/src/pricing/pricing.ts: -------------------------------------------------------------------------------- 1 | export interface PricingInterface { 2 | [key: string]: { 3 | pricing: { 4 | monthly: number; 5 | yearly: number; 6 | }, 7 | faq: number; 8 | user: number; 9 | integrations: number; 10 | categories: number; 11 | domains: number; 12 | package: string; 13 | embed: boolean; 14 | api: boolean; 15 | } 16 | } 17 | export const pricing: PricingInterface = { 18 | FREE: { 19 | pricing: { 20 | monthly: 0, 21 | yearly: 0, 22 | }, 23 | faq: 10, 24 | user: 1, 25 | integrations: 1, 26 | categories: 5, 27 | domains: 0, 28 | package: 'free', 29 | embed: false, 30 | api: false, 31 | }, 32 | BASIC: { 33 | pricing: { 34 | monthly: 50, 35 | yearly: 500, 36 | }, 37 | faq: 20, 38 | user: 3, 39 | integrations: 3, 40 | categories: 10, 41 | domains: 1, 42 | package: 'basic', 43 | embed: false, 44 | api: false, 45 | }, 46 | PRO: { 47 | pricing: { 48 | monthly: 100, 49 | yearly: 1000, 50 | }, 51 | user: 5, 52 | faq: 50, 53 | integrations: 7, 54 | categories: 20, 55 | domains: 1, 56 | package: 'pro', 57 | embed: true, 58 | api: true, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /libraries/helpers/src/revalidate/revalidate.service.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import {Injectable} from "@nestjs/common"; 3 | 4 | @Injectable() 5 | export class RevalidateService { 6 | async revalidate(name: string) { 7 | try { 8 | console.log(process.env.REVALIDATE_URL + '/api/revalidate'); 9 | await axios.post(process.env.REVALIDATE_URL + '/api/revalidate', { 10 | name, 11 | }, { 12 | headers: { 13 | serverkey: process.env.BACKEND_TOKEN_PROTECTOR, 14 | } 15 | }); 16 | } 17 | catch (err) { 18 | return ; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /libraries/helpers/src/slack/slack.props.ts: -------------------------------------------------------------------------------- 1 | export const slackProps = { 2 | clientId: process.env.SLACK_CLIENT_ID!, 3 | clientSecret: process.env.SLACK_CLIENT_SECRET!, 4 | stateSecret: process.env.BACKEND_TOKEN_PROTECTOR!, 5 | scopes: [ 6 | 'channels:history', 7 | 'chat:write', 8 | 'chat:write.public', 9 | 'groups:history', 10 | 'im:history', 11 | 'im:write', 12 | 'links:read', 13 | 'mpim:history', 14 | 'users.profile:read', 15 | 'commands', 16 | 'files:read', 17 | 'team:read', 18 | ], 19 | userScopes: ['identify', 'files:read', 'files:write'], 20 | }; 21 | -------------------------------------------------------------------------------- /libraries/helpers/src/slack/slack.service.ts: -------------------------------------------------------------------------------- 1 | import { WebAPICallResult, WebClient, WebClientOptions } from "@slack/web-api"; 2 | import { Installation } from "@slack/oauth"; 3 | import { Enterprise } from "@slack/web-api/dist/response/OauthV2AccessResponse"; 4 | 5 | interface AuthTestResult extends WebAPICallResult { 6 | bot_id?: string; 7 | url?: string; 8 | } 9 | 10 | const client = new WebClient(); 11 | export class SlackService { 12 | accessToken: string; 13 | static async load(code: string) { 14 | const resp = await client.oauth.v2.access({ 15 | client_id: process.env.SLACK_CLIENT_ID!, 16 | client_secret: process.env.SLACK_CLIENT_SECRET!, 17 | code, 18 | redirect_uri: process?.env?.FRONTEND_URL?.indexOf('localhost')! > -1 ? `https://redirectmeto.com/${process.env.FRONTEND_URL}/api/integrations/slack` : `${process.env.FRONTEND_URL}/api/integrations/slack` 19 | }); 20 | 21 | const authResult = await SlackService.runAuthTest(resp.access_token!, {}); 22 | 23 | return { 24 | user: { 25 | id: resp?.authed_user?.id!, 26 | }, 27 | team: { 28 | id: resp?.team?.id!, 29 | name: resp?.team?.name!, 30 | }, 31 | bot: { 32 | userId: resp.bot_user_id, 33 | id: authResult.bot_id as string, 34 | token: resp?.access_token!, 35 | userToken: resp?.authed_user?.access_token 36 | } 37 | } 38 | } 39 | static async runAuthTest(token: string, clientOptions: WebClientOptions): Promise { 40 | const client = new WebClient(token, clientOptions); 41 | const authResult = await client.auth.test(); 42 | return authResult as unknown as AuthTestResult; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /libraries/helpers/src/subdomain/subdomain.management.ts: -------------------------------------------------------------------------------- 1 | import {allTwoLevelSubdomain} from "./all.two.level.subdomain"; 2 | 3 | export function removeSubdomain(domain: string) { 4 | // Split the domain into its parts 5 | const parts = domain.split('.'); 6 | 7 | // Check if there are at least two parts (e.g., 'example.com') 8 | if (parts.length < 2) { 9 | return domain; // Return the original domain if it's too short to have a subdomain 10 | } 11 | 12 | if (parts.length > 2) { 13 | const lastTwo = parts.slice(-2).join('.'); 14 | if (allTwoLevelSubdomain.includes(lastTwo)) { 15 | return 'https://' + parts.slice(-3).join('.'); // Return the last three parts for known second-level domains 16 | } 17 | } 18 | 19 | // Return the last two parts for standard domains 20 | return 'https://' + parts.slice(-2).join('.'); 21 | } 22 | -------------------------------------------------------------------------------- /libraries/helpers/src/swagger/load.swagger.ts: -------------------------------------------------------------------------------- 1 | import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger"; 2 | import {INestApplication} from "@nestjs/common"; 3 | // import load from '@crosspublic/backend/src/metadata'; 4 | 5 | export const loadSwagger = (app: INestApplication) => { 6 | const config = new DocumentBuilder() 7 | .setTitle('crosspublic Swagger file') 8 | .setDescription('API description') 9 | .setVersion('1.0') 10 | .build(); 11 | 12 | const document = SwaggerModule.createDocument(app, config); 13 | SwaggerModule.setup('docs', app, document); 14 | } 15 | -------------------------------------------------------------------------------- /libraries/helpers/src/user/organization.from.request.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | import {Organization} from "@prisma/client"; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 5 | export interface OrganizationFromRequest extends Organization { 6 | } 7 | 8 | export const GetOrganizationFromRequest = createParamDecorator( 9 | (data: unknown, ctx: ExecutionContext) => { 10 | const request = ctx.switchToHttp().getRequest(); 11 | return request.organization; 12 | } 13 | ); 14 | -------------------------------------------------------------------------------- /libraries/helpers/src/user/user.from.request.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'; 2 | 3 | export const GetUserFromRequest = createParamDecorator( 4 | (data: unknown, ctx: ExecutionContext) => { 5 | const request = ctx.switchToHttp().getRequest(); 6 | return request.user; 7 | } 8 | ); 9 | -------------------------------------------------------------------------------- /libraries/helpers/src/user/user.interface.ts: -------------------------------------------------------------------------------- 1 | export interface UserInterface { 2 | id: string, 3 | name: string, 4 | email: string, 5 | internalId: string, 6 | organization: { 7 | userId: string, 8 | organizationId: string, 9 | role: 'ADMIN' | 'USER' 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libraries/helpers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noImplicitReturns": true, 9 | "noFallthroughCasesInSwitch": true 10 | }, 11 | "files": [], 12 | "include": [], 13 | "references": [ 14 | { 15 | "path": "./tsconfig.lib.json" 16 | }, 17 | { 18 | "path": "./tsconfig.spec.json" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /libraries/helpers/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"], 7 | "target": "es2021", 8 | "strictNullChecks": true, 9 | "noImplicitAny": true, 10 | "strictBindCallApply": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noFallthroughCasesInSwitch": true 13 | }, 14 | "include": ["src/**/*.ts"], 15 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /libraries/helpers/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /libraries/validators/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "rules": { 5 | "@typescript-eslint/no-explicit-any": "off" 6 | }, 7 | "overrides": [ 8 | { 9 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 10 | "rules": {} 11 | }, 12 | { 13 | "files": ["*.ts", "*.tsx"], 14 | "rules": {} 15 | }, 16 | { 17 | "files": ["*.js", "*.jsx"], 18 | "rules": {} 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /libraries/validators/README.md: -------------------------------------------------------------------------------- 1 | # validators 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test validators` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /libraries/validators/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'validators', 4 | preset: '../../jest.preset.js', 5 | testEnvironment: 'node', 6 | transform: { 7 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 8 | }, 9 | moduleFileExtensions: ['ts', 'js', 'html'], 10 | coverageDirectory: '../../coverage/libraries/validators', 11 | }; 12 | -------------------------------------------------------------------------------- /libraries/validators/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "validators", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "libraries/validators/src", 5 | "projectType": "library", 6 | "targets": { 7 | "lint": { 8 | "executor": "@nx/eslint:lint", 9 | "outputs": ["{options.outputFile}"], 10 | "options": { 11 | "lintFilePatterns": ["libraries/validators/**/*.ts"] 12 | } 13 | }, 14 | "test": { 15 | "executor": "@nx/jest:jest", 16 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 17 | "options": { 18 | "jestConfig": "libraries/validators/jest.config.ts" 19 | } 20 | } 21 | }, 22 | "tags": [] 23 | } 24 | -------------------------------------------------------------------------------- /libraries/validators/src/auth/auth.integration.validator.ts: -------------------------------------------------------------------------------- 1 | import { IsDefined, IsIn, IsString } from "class-validator"; 2 | import {IntegrationType} from '@prisma/client'; 3 | 4 | export class AuthIntegrationValidator { 5 | @IsDefined() 6 | @IsIn([IntegrationType.DISCORD, IntegrationType.SLACK]) 7 | type: IntegrationType; 8 | 9 | @IsDefined() 10 | @IsString() 11 | guild: string; 12 | 13 | @IsDefined() 14 | @IsString() 15 | user: string; 16 | } 17 | -------------------------------------------------------------------------------- /libraries/validators/src/auth/login.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsDefined, IsEmail, IsString, MinLength} from "class-validator"; 2 | 3 | export class LoginValidator { 4 | @IsDefined() 5 | @IsEmail() 6 | email: string; 7 | 8 | @IsDefined() 9 | @IsString() 10 | @MinLength(3) 11 | password: string; 12 | } 13 | -------------------------------------------------------------------------------- /libraries/validators/src/auth/registration.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsDefined, IsEmail, IsOptional, IsString, MinLength} from "class-validator"; 2 | 3 | export class RegistrationValidator { 4 | @IsDefined() 5 | @IsEmail() 6 | email: string; 7 | 8 | @IsDefined() 9 | @IsString() 10 | @MinLength(3) 11 | password: string; 12 | 13 | @IsDefined() 14 | @IsString() 15 | @MinLength(3) 16 | company: string; 17 | 18 | @IsOptional() 19 | @IsString() 20 | token?: string; 21 | } 22 | -------------------------------------------------------------------------------- /libraries/validators/src/billing/billing.subscribe.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsIn} from "class-validator"; 2 | import {ApiProperty} from "@nestjs/swagger"; 3 | 4 | export class BillingSubscribeValidator { 5 | @ApiProperty() 6 | @IsIn(['MONTHLY', 'YEARLY']) 7 | period: 'MONTHLY' | 'YEARLY'; 8 | 9 | @IsIn(['BASIC', 'PRO']) 10 | billing: 'BASIC' | 'PRO'; 11 | } 12 | -------------------------------------------------------------------------------- /libraries/validators/src/categories/create.category.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsString, MinLength} from "class-validator"; 2 | 3 | export class CreateCategoryValidator { 4 | @IsString() 5 | @MinLength(3) 6 | name: string; 7 | 8 | @IsString() 9 | @MinLength(3) 10 | editor: string; 11 | } 12 | -------------------------------------------------------------------------------- /libraries/validators/src/categories/delete.category.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsOptional, IsString} from "class-validator"; 2 | 3 | export class DeleteCategoryValidator { 4 | @IsString() 5 | @IsOptional() 6 | category: string; 7 | } 8 | -------------------------------------------------------------------------------- /libraries/validators/src/faq/create.faq.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsString, MinLength} from "class-validator"; 2 | import {ApiProperty} from "@nestjs/swagger"; 3 | 4 | export class CreateFaqValidator { 5 | @ApiProperty() 6 | @IsString() 7 | @MinLength(3) 8 | question: string; 9 | 10 | @IsString() 11 | @MinLength(3) 12 | answer: string; 13 | 14 | @IsString() 15 | categoryId: string; 16 | } 17 | -------------------------------------------------------------------------------- /libraries/validators/src/general/category.string.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsString} from "class-validator"; 2 | import {IdStringValidator} from "./id.string.validator"; 3 | 4 | export class CategoryStringValidator extends IdStringValidator { 5 | @IsString() 6 | category: string; 7 | } 8 | -------------------------------------------------------------------------------- /libraries/validators/src/general/id.string.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsString} from "class-validator"; 2 | 3 | export class IdStringValidator { 4 | @IsString() 5 | id: string; 6 | } 7 | -------------------------------------------------------------------------------- /libraries/validators/src/general/order.validator.ts: -------------------------------------------------------------------------------- 1 | import {Type} from "class-transformer"; 2 | import {IsArray, IsNumber, IsString, ValidateNested} from "class-validator"; 3 | 4 | export class OrderValidator { 5 | @IsArray() 6 | @ValidateNested() 7 | @Type(() => OrderListValidator) 8 | order: OrderListValidator[]; 9 | } 10 | 11 | export class OrderListValidator { 12 | @IsString() 13 | id: string; 14 | 15 | @IsNumber() 16 | order: number; 17 | } 18 | -------------------------------------------------------------------------------- /libraries/validators/src/integrations/create.integration.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsDefined, IsIn, IsString, ValidateNested} from "class-validator"; 2 | import {Type} from "class-transformer"; 3 | 4 | export abstract class Provider { 5 | } 6 | 7 | export class Slack extends Provider { 8 | @IsString() 9 | @IsDefined() 10 | code: string; 11 | } 12 | 13 | export class Discord extends Provider{ 14 | @IsString() 15 | @IsDefined() 16 | code: string; 17 | 18 | @IsString() 19 | @IsDefined() 20 | guild_id: string; 21 | 22 | @IsString() 23 | @IsDefined() 24 | permissions: string; 25 | } 26 | export class CreateIntegrationValidator { 27 | @IsIn(['discord', 'slack']) 28 | type: 'discord' | 'slack'; 29 | 30 | @ValidateNested() 31 | @IsDefined() 32 | @Type(() => Provider, { 33 | discriminator: { 34 | property: '__type', 35 | subTypes: [ 36 | { value: Discord, name: 'discord' }, 37 | { value: Slack, name: 'slack' }, 38 | ], 39 | }, 40 | }) 41 | information: Discord | Slack; 42 | } 43 | -------------------------------------------------------------------------------- /libraries/validators/src/integrations/integrations.type.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsDefined, IsIn} from "class-validator"; 2 | 3 | export class IntegrationsTypeValidator { 4 | @IsDefined() 5 | @IsIn(['slack', 'discord']) 6 | type: string; 7 | } 8 | -------------------------------------------------------------------------------- /libraries/validators/src/invitations/create.invitations.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsDefined, IsIn, IsString, ValidateNested} from "class-validator"; 2 | import {Type} from "class-transformer"; 3 | 4 | export abstract class Provider { 5 | } 6 | 7 | export class Slack extends Provider { 8 | @IsString() 9 | @IsDefined() 10 | internalId: string; 11 | 12 | @IsString() 13 | @IsDefined() 14 | guild: string; 15 | } 16 | 17 | export class Discord extends Provider{ 18 | @IsString() 19 | @IsDefined() 20 | internalId: string; 21 | 22 | @IsString() 23 | @IsDefined() 24 | guild: string; 25 | } 26 | 27 | export class InvitationsValidator { 28 | @IsIn(['discord', 'slack']) 29 | type: 'discord' | 'slack'; 30 | 31 | @ValidateNested() 32 | @IsDefined() 33 | @Type(() => Provider, { 34 | discriminator: { 35 | property: '__type', 36 | subTypes: [ 37 | { value: Discord, name: 'discord' }, 38 | { value: Slack, name: 'slack' }, 39 | ], 40 | }, 41 | }) 42 | information: Discord | Slack; 43 | } 44 | -------------------------------------------------------------------------------- /libraries/validators/src/messages/messages.list.validator.ts: -------------------------------------------------------------------------------- 1 | import {ArrayMinSize, IsArray, IsString, ValidateNested} from "class-validator"; 2 | import {Type} from "class-transformer"; 3 | 4 | export class OnlyMessagesList { 5 | @IsArray() 6 | @ValidateNested() 7 | @ArrayMinSize(2) 8 | @Type(() => MessageList) 9 | messagesList: MessageList[]; 10 | } 11 | 12 | export class AnsweredQuestion extends OnlyMessagesList { 13 | @IsString() 14 | question: string; 15 | } 16 | 17 | export class MessagesListValidator extends OnlyMessagesList { 18 | @IsString() 19 | reference: string; 20 | } 21 | 22 | export class MessageList { 23 | @IsString() 24 | name: string; 25 | 26 | @IsString() 27 | message: string; 28 | } 29 | -------------------------------------------------------------------------------- /libraries/validators/src/organizations/organization.create.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsString} from "class-validator"; 2 | 3 | export class OrganizationCreateValidator { 4 | @IsString() 5 | guildId: string; 6 | 7 | @IsString() 8 | internalId: string; 9 | } 10 | -------------------------------------------------------------------------------- /libraries/validators/src/organizations/update.style.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsDefined, IsOptional, IsString, MinLength, registerDecorator, ValidationArguments, ValidationOptions, ValidatorConstraint, ValidatorConstraintInterface} from "class-validator"; 2 | 3 | @ValidatorConstraint({ async: false }) 4 | class IsHexColorConstraint implements ValidatorConstraintInterface { 5 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 6 | validate(hexColor: unknown) { 7 | return typeof hexColor === 'string' && /^#(?:[0-9a-fA-F]{3}){1,2}$/.test(hexColor); 8 | } 9 | 10 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 11 | defaultMessage() { 12 | return 'Text ($value) is not a valid hex color'; 13 | } 14 | } 15 | 16 | export function IsHexColor(validationOptions?: ValidationOptions) { 17 | return function (object: any, propertyName: string) { 18 | registerDecorator({ 19 | target: object.constructor, 20 | propertyName: propertyName, 21 | options: validationOptions, 22 | constraints: [], 23 | validator: IsHexColorConstraint, 24 | }); 25 | }; 26 | } 27 | 28 | export class UpdateStyleValidator { 29 | @IsDefined() 30 | @IsString() 31 | @MinLength(1) 32 | name: string; 33 | 34 | @IsDefined() 35 | @IsString() 36 | @IsHexColor() 37 | topBarColor: string; 38 | 39 | @IsDefined() 40 | @IsString() 41 | @IsHexColor() 42 | topBarTextColor: string; 43 | 44 | @IsDefined() 45 | @IsString() 46 | @IsHexColor() 47 | backgroundColor: string; 48 | 49 | @IsDefined() 50 | @IsString() 51 | @IsHexColor() 52 | pageTextColor: string; 53 | 54 | @IsDefined() 55 | @IsString() 56 | @IsHexColor() 57 | pageBlockColor: string; 58 | 59 | @IsString() 60 | @IsOptional() 61 | brandingText: string; 62 | } 63 | -------------------------------------------------------------------------------- /libraries/validators/src/public/domain.subDomain.organization.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsString, ValidateIf} from "class-validator"; 2 | 3 | export class DomainSubDomainOrganizationValidator { 4 | @ValidateIf(o => !o.subdomain) 5 | @IsString() 6 | domain: string; 7 | 8 | @ValidateIf(o => !o.domain) 9 | @IsString() 10 | subdomain: string; 11 | } 12 | -------------------------------------------------------------------------------- /libraries/validators/src/settings/add.domain.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsString, Matches} from 'class-validator'; 2 | 3 | export class AddDomainValidator { 4 | @Matches(/^(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, { 5 | message: 'Invalid domain format' 6 | }) 7 | @IsString() 8 | domain: string; 9 | } 10 | -------------------------------------------------------------------------------- /libraries/validators/src/settings/check.subdomain.validator.ts: -------------------------------------------------------------------------------- 1 | import {IsString, MinLength} from "class-validator"; 2 | 3 | export class CheckSubdomainValidator { 4 | @IsString() 5 | @MinLength(3) 6 | subDomain: string; 7 | } 8 | -------------------------------------------------------------------------------- /libraries/validators/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true, 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /libraries/validators/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"], 7 | "target": "es2021", 8 | "strictNullChecks": true, 9 | "noImplicitAny": true, 10 | "strictBindCallApply": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noFallthroughCasesInSwitch": true 13 | }, 14 | "include": ["src/**/*.ts"], 15 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /libraries/validators/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "migrations": [ 3 | { 4 | "version": "17.1.0-beta.2", 5 | "description": "Move jest executor options to nx.json targetDefaults", 6 | "implementation": "./src/migrations/update-17-1-0/move-options-to-target-defaults", 7 | "package": "@nx/jest", 8 | "name": "move-options-to-target-defaults" 9 | }, 10 | { 11 | "version": "17.1.0-beta.1", 12 | "description": "Updates for @typescript-utils/utils v6.9.1+", 13 | "implementation": "./src/migrations/update-17-1-0/update-typescript-eslint", 14 | "package": "@nx/eslint", 15 | "name": "update-typescript-eslint" 16 | }, 17 | { 18 | "cli": "nx", 19 | "version": "17.0.2", 20 | "description": "Remove deprecated build options", 21 | "implementation": "./src/migrations/update-17-0-0/remove-deprecated-build-options", 22 | "package": "@nx/js", 23 | "name": "update-17-0-0-remove-deprecated-build-options" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "targetDefaults": { 4 | "build": { 5 | "cache": true, 6 | "dependsOn": ["^build"], 7 | "inputs": ["production", "^production"] 8 | }, 9 | "lint": { 10 | "cache": true, 11 | "inputs": [ 12 | "default", 13 | "{workspaceRoot}/.eslintrc.json", 14 | "{workspaceRoot}/.eslintignore", 15 | "{workspaceRoot}/eslint.config.js" 16 | ] 17 | }, 18 | "@nx/jest:jest": { 19 | "cache": true, 20 | "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"], 21 | "options": { 22 | "passWithNoTests": true 23 | }, 24 | "configurations": { 25 | "ci": { 26 | "ci": true, 27 | "codeCoverage": true 28 | } 29 | } 30 | } 31 | }, 32 | "namedInputs": { 33 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 34 | "production": [ 35 | "default", 36 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 37 | "!{projectRoot}/tsconfig.spec.json", 38 | "!{projectRoot}/jest.config.[jt]s", 39 | "!{projectRoot}/src/test-setup.[jt]s", 40 | "!{projectRoot}/test-setup.[jt]s", 41 | "!{projectRoot}/.eslintrc.json", 42 | "!{projectRoot}/eslint.config.js" 43 | ], 44 | "sharedGlobals": [] 45 | }, 46 | "generators": { 47 | "@nx/react": { 48 | "application": { 49 | "babel": true 50 | } 51 | }, 52 | "@nx/next": { 53 | "application": { 54 | "style": "scss", 55 | "linter": "eslint" 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "strictPropertyInitialization": false, 11 | "importHelpers": true, 12 | "target": "es2015", 13 | "module": "esnext", 14 | "lib": ["es2020", "dom"], 15 | "skipLibCheck": true, 16 | "skipDefaultLibCheck": true, 17 | "noPropertyAccessFromIndexSignature": false, 18 | "baseUrl": ".", 19 | "paths": { 20 | "@crosspublic/backend/*": ["apps/backend/*"], 21 | "@crosspublic/database/*": ["libraries/database/*"], 22 | "@crosspublic/communication/*": ["apps/communication/*"], 23 | "@crosspublic/helpers/*": ["libraries/helpers/*"], 24 | "@crosspublic/validators/*": ["libraries/validators/*"], 25 | "@crosspublic/panel/*": ["apps/panel/*"], 26 | "@crosspublic/tenants/*": ["apps/tenants/*"], 27 | "@crosspublic/marketing/*": ["apps/marketing/*"], 28 | "@crosspublic/docs/*": ["apps/docs/*"], 29 | }, 30 | "resolveJsonModule": true 31 | }, 32 | "exclude": ["node_modules", "tmp"] 33 | } 34 | --------------------------------------------------------------------------------