Compare commits
419 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dbc2e3f713 | |||
| a5b49cb61f | |||
| 096151c014 | |||
| 5d28f1a3d6 | |||
| 56ae0ed0d3 | |||
| a2f5dc8bc6 | |||
| d56b2eded5 | |||
| 0ae9adafbf | |||
| 03403ad9cb | |||
| 067e19fec9 | |||
| 2cce68b5f7 | |||
| e3c3aa91e1 | |||
| 5c0ca83852 | |||
| 2b24104c97 | |||
| 5977a5aa51 | |||
| 131d0dbea4 | |||
| 59277731c4 | |||
| 742d16087b | |||
| 0ab51d62bc | |||
| 756024da78 | |||
| c47ad5e038 | |||
| 888344797e | |||
| 4a7caa68b6 | |||
| 3bc54d36f7 | |||
| 5295a3f798 | |||
| 14429b8b66 | |||
| 5dd1bcc589 | |||
| ed5ee16de5 | |||
| 076185d19f | |||
| 1e07964d81 | |||
| f406e59c45 | |||
| 75550f535c | |||
| 2b48e6d04b | |||
| fe14947bb0 | |||
| 0afa119721 | |||
| af57bc208a | |||
| 7cbbb26038 | |||
| 2e5f8d8de3 | |||
| 9a7c08c26a | |||
| e8e7c116d1 | |||
| 99dd6673f9 | |||
| a41d9b3e67 | |||
| cb1825bfaf | |||
| d02173442c | |||
| 9f1f59cc81 | |||
| e48c9b5e69 | |||
| 55a9b96c88 | |||
| dc8c8edf04 | |||
| 41927edf12 | |||
| 1afba46cf0 | |||
| 32e4fc6310 | |||
| 0add95e224 | |||
| de7f133ef9 | |||
| 8b13681afc | |||
| 88baac2144 | |||
| df664ad472 | |||
| d8c53784f7 | |||
| 3c7b2de095 | |||
| ae9249405c | |||
| d63f4e2451 | |||
| f4275e5054 | |||
| 690b308fea | |||
| a74716a1b9 | |||
| 42185bc36b | |||
| 276181b91c | |||
| 578d2e5410 | |||
| 329944841f | |||
| 4a0f030ebf | |||
| 13345d0025 | |||
| d905059915 | |||
| 0d8530028f | |||
| c37e7f3030 | |||
| 6d400c3b57 | |||
| 2b8a44aed1 | |||
| 6d842b3abb | |||
| 9c6838e5c8 | |||
| 10e935b864 | |||
| 02e453c3b5 | |||
| 330ddb3311 | |||
| 14a2f4cc94 | |||
| 7e57f1f3dd | |||
| cf8f994604 | |||
| 2ae5178479 | |||
| 620b83832b | |||
| c7434125e4 | |||
| 67b5365d50 | |||
| ea25c37a10 | |||
| c1710ae72d | |||
| 9c5a7cf073 | |||
| 8eedf994af | |||
| 2c91c492df | |||
| b859abdb66 | |||
| 0876b87526 | |||
| 9c4be40adc | |||
| 2635f4bd10 | |||
| 401b7ee03d | |||
| 6881de84ab | |||
| 0ca8296b9a | |||
| 55946fcdb7 | |||
| c0f9f10b07 | |||
| e80aa2826d | |||
| 1be25d7639 | |||
| 3c3a5e1f28 | |||
| 8e741c7218 | |||
| 83455e3ad4 | |||
| f0f0420280 | |||
| c4533c36cb | |||
| f8e034c190 | |||
| 21f62e780c | |||
| c3564ca0d4 | |||
| 6e1b8a11d4 | |||
| 9900f19b70 | |||
| 237545754f | |||
| d7c1e98e5e | |||
| 71a0afeb8d | |||
| 36a390449a | |||
| 375e495423 | |||
| 866c20cd39 | |||
| 408785b8ef | |||
| f982e00997 | |||
| 7e100b8362 | |||
| ea052c44c7 | |||
| 76224568f4 | |||
| 72e67981b0 | |||
| cc44aff24b | |||
| 07d28ee4f5 | |||
| f16a2cc4e6 | |||
| b859837564 | |||
| 1343003efa | |||
| 0c4aea0338 | |||
| 627c767c64 | |||
| f3a0c3a5d1 | |||
| 7bca3d9c15 | |||
| 01fc1c6634 | |||
| 2d4f85e9f4 | |||
| 8718b28949 | |||
| 2caa2a5bbd | |||
| 43a10ee695 | |||
| 018005d478 | |||
| 4da187c4c5 | |||
| 4925424778 | |||
| 7ef61222ed | |||
| 044d38e390 | |||
| e7768881af | |||
| 5ea6a49d08 | |||
| dce5578327 | |||
| fde3f17a25 | |||
| a6c9cac26c | |||
| 960d99461a | |||
| e5307133b3 | |||
| 34bb3acc23 | |||
| 6d3b761cd7 | |||
| abc9737a4f | |||
| 84af64165c | |||
| 0adf41c7f0 | |||
| b7f4e40edb | |||
| cf33396dc0 | |||
| 51f2cd6b3f | |||
| 513d0f071d | |||
| 50e3192453 | |||
| 1524c95a7a | |||
| c8aaea8ad0 | |||
| cf7f25abcd | |||
| 99453e8581 | |||
| bc7206d981 | |||
| 64b8e150ea | |||
| b3b5852a88 | |||
| d1ecd6b99c | |||
| 0fb16a7324 | |||
| 4f317055fd | |||
| 3afd5f8cf3 | |||
| c922d24941 | |||
| 39ec34f84f | |||
| b9eba68078 | |||
| 9df93d0798 | |||
| 6769d6b439 | |||
| 70410d33af | |||
| 6371dd4f95 | |||
| ebe78017ef | |||
| b697a19954 | |||
| 29ca09e42d | |||
| 375b788ab0 | |||
| d6b44d13f7 | |||
| c6784ba7b8 | |||
| 6b8b1c7cae | |||
| 0c20756624 | |||
| 3d387d602d | |||
| 5d45dea0e1 | |||
| a25751e805 | |||
| 659be8cbc1 | |||
| 50e3d9e43a | |||
| 203a431350 | |||
| 39a332eff1 | |||
| 498f56de3e | |||
| b23696b255 | |||
| f3a7e2aa20 | |||
| b737f7af92 | |||
| f54a9ff5fc | |||
| 067c0db543 | |||
| 773765f82d | |||
| 624072c23b | |||
| d37576f814 | |||
| 476e2f035f | |||
| ad71964d30 | |||
| 975c5f1d3c | |||
| 916ed4e366 | |||
| df4667b7f5 | |||
| d663ba8985 | |||
| 8c8d1598e3 | |||
| 646bfd37bd | |||
| c0d5292b06 | |||
| 8c383bdc69 | |||
| 97e63d1222 | |||
| 87ee98aff1 | |||
| c070e78d06 | |||
| 1c4f9ff1ff | |||
| ca031a72e7 | |||
| cbc976188e | |||
| 59a94c710e | |||
| 4cd97c81ea | |||
| d0d5a698ec | |||
| ee033a1fd4 | |||
| 2394fcdf35 | |||
| 6edaffeb7e | |||
| 0558b2c595 | |||
| b12bc6042f | |||
| a7bc5df754 | |||
| 1c524c5acc | |||
| 65ad98c20d | |||
| 24d892e322 | |||
| be9b51131d | |||
| 63a65dabe4 | |||
| 1f3b423c22 | |||
| a69a5019f1 | |||
| 5910339bf4 | |||
| 4b641b25a6 | |||
| a847b492bc | |||
| 0df6410561 | |||
| c17faabe9f | |||
| 9a187f9eda | |||
| 2e9452ab7c | |||
| 914745f642 | |||
| 9189bdf50f | |||
| 3e5a97c4b5 | |||
| c0aa6633d2 | |||
| 4e35871861 | |||
| 90f52de147 | |||
| 28b05a82d8 | |||
| b9ed7e2ced | |||
| 5e651d84af | |||
| 6a992a1995 | |||
| 81e886a1ba | |||
| ab9e4a989c | |||
| e350659e24 | |||
| f888a49555 | |||
| 421c249e50 | |||
| a8a6f51387 | |||
| 12e73eef22 | |||
| 2e33fcb16b | |||
| 5912cdd11c | |||
| cc016b3ae3 | |||
| 7760e09462 | |||
| 3ca4456c88 | |||
| 618b7f22da | |||
| a12c7c3b0a | |||
| a42e94c75f | |||
| bc757c8ddb | |||
| 6e11ab9183 | |||
| 2397fcb640 | |||
| 35d0c2a06f | |||
| 813ec74672 | |||
| 03db13171a | |||
| 0ecb5129e8 | |||
| 44cb28c1c4 | |||
| 4578f6bd64 | |||
| 8b25e57613 | |||
| 8fb9ea19a3 | |||
| eb4ca0ce2d | |||
| df24cd5b71 | |||
| 284324031c | |||
| 2fdf42d444 | |||
| 50137a6f24 | |||
| 3703f989fa | |||
| 2a42942b01 | |||
| 7b29a92f6a | |||
| 24465c9a7b | |||
| 42022b6ba4 | |||
| dcc807d716 | |||
| b5e37bfa61 | |||
| 5681ebc756 | |||
| 886d6fda36 | |||
| 1a4952720f | |||
| c0dfa8048a | |||
| 3e63a7e7a0 | |||
| 4cc690a6ce | |||
| 537447e318 | |||
| 15f17d27e0 | |||
| 91fc1e80f3 | |||
| dfe680a390 | |||
| f73a2a424b | |||
| 6ce87f169a | |||
| ad14983e21 | |||
| 4edc7112ab | |||
| c2ce707f3d | |||
| cd70a29970 | |||
| cd94d8fee2 | |||
| 241e40e3e2 | |||
| 486ed20ae7 | |||
| e5ffc3a3f3 | |||
| 363c74c6da | |||
| f98c425186 | |||
| da10ba907f | |||
| 5aeef1233b | |||
| fb5f4e308f | |||
| 6ccb4fbc63 | |||
| 88ebf4a1a0 | |||
| 9a0e6412e9 | |||
| a68a912d56 | |||
| dde51d6d25 | |||
| dbd094216d | |||
| dc7f335beb | |||
| 454cad69b4 | |||
| f86117f92b | |||
| b4c67984bc | |||
| 1b4db79885 | |||
| e1e60499b2 | |||
| 4eb2439d64 | |||
| 2895ad52fd | |||
| a73b842c60 | |||
| cfa3e24123 | |||
| b2670f5948 | |||
| 5a93aa6fa1 | |||
| 6b471489b0 | |||
| 07d68a3289 | |||
| bf24593b7f | |||
| f52ecb9a84 | |||
| 711019032f | |||
| 68c3c89824 | |||
| b00717748b | |||
| 7833db45d7 | |||
| 6de8bd470b | |||
| 1d4e31138e | |||
| a93fba932c | |||
| 6d29914327 | |||
| bd0b335c1e | |||
| 82c5965e8a | |||
| 65fa8300ad | |||
| aae143d96e | |||
| 315b94d05b | |||
| 440560976d | |||
| 5c1e002233 | |||
| 36f2ece44c | |||
| 316bf9f3c2 | |||
| a57221bbfe | |||
| 39b8445e06 | |||
| 33ecb36be1 | |||
| a00fbe421a | |||
| c0edeca5cc | |||
| 7ac7d83c3e | |||
| 3d1b6c236a | |||
| 8ddd151051 | |||
| 9947383f5a | |||
| 46e1a1493b | |||
| b1cdbf52d6 | |||
| 1491d4251d | |||
| 62ac5dd137 | |||
| 23c93a5712 | |||
| 208907e10e | |||
| 3ef588e4e5 | |||
| 10836735be | |||
| 090bc2e7d2 | |||
| 4fe2fd63de | |||
| 145fb24e4d | |||
| 2382d4a5c5 | |||
| adbcfe6f20 | |||
| 0754e99507 | |||
| dfb0a79abc | |||
| c808e40f61 | |||
| a9bb53b529 | |||
| 2636d9091e | |||
| 878da3c01f | |||
| 588461188f | |||
| d4145de9a7 | |||
| 188c4b0e24 | |||
| ecd050bece | |||
| b24aeea592 | |||
| 30ba73c4f1 | |||
| bf6a2a0319 | |||
| 4fc5de0dac | |||
| 11ff63a9d2 | |||
| 0d1bdac8ab | |||
| 9f076631ae | |||
| a0d663c0fb | |||
| 8fd68e30e9 | |||
| 5c13512091 | |||
| 1d7e5737a0 | |||
| 305e3b7ef2 | |||
| 03721cb18d | |||
| 773a6e2c81 | |||
| 2f13ac100f | |||
| a10782490f | |||
| 6ad91123f2 | |||
| 81f2d5e4ec | |||
| f249d8defa | |||
| 09aef5cda7 | |||
| 3028b6ad9d | |||
| 060b650845 | |||
| 6186a14398 | |||
| 916a353660 | |||
| 699878a5b1 | |||
| d2c9b64212 | |||
| 3102deccfb | |||
| 97c1b51332 | |||
| 4ab7082954 | |||
| 760f5de248 | |||
| de7e8c783e | |||
| 45b7af53e6 | |||
| aa7458fe37 | |||
| 1fd1d26780 |
+14
-3
@@ -2,10 +2,21 @@
|
||||
"$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json",
|
||||
"changelog": "@changesets/cli/changelog",
|
||||
"commit": false,
|
||||
"fixed": [["@trigger.dev/*"]],
|
||||
"fixed": [
|
||||
[
|
||||
"@trigger.dev/*"
|
||||
]
|
||||
],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": ["webapp", "emails", "@trigger.dev/database"]
|
||||
}
|
||||
"ignore": [
|
||||
"webapp",
|
||||
"emails",
|
||||
"@trigger.dev/database"
|
||||
],
|
||||
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
|
||||
"onlyUpdatePeerDependentsWhenOutOfRange": true
|
||||
}
|
||||
}
|
||||
+13
-4
@@ -1,35 +1,43 @@
|
||||
*.log
|
||||
\*.log
|
||||
.git
|
||||
.github
|
||||
|
||||
# editor
|
||||
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# dependencies
|
||||
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
|
||||
coverage
|
||||
|
||||
# next.js
|
||||
|
||||
.next/
|
||||
build
|
||||
|
||||
# packages
|
||||
|
||||
build
|
||||
dist
|
||||
packages/**/dist
|
||||
packages/\*\*/dist
|
||||
|
||||
# misc
|
||||
|
||||
.DS_Store
|
||||
*.pem
|
||||
\*.pem
|
||||
|
||||
.turbo
|
||||
.vercel
|
||||
.cache
|
||||
.output
|
||||
apps/**/public/build
|
||||
apps/\*\*/public/build
|
||||
|
||||
cypress/screenshots
|
||||
cypress/videos
|
||||
@@ -38,6 +46,7 @@ apps/**/styles/tailwind.css
|
||||
packages/**/styles/tailwind.css
|
||||
|
||||
.changeset
|
||||
references
|
||||
examples
|
||||
CHANGESETS.md
|
||||
CONTRIBUTING.md
|
||||
|
||||
@@ -13,6 +13,8 @@ APP_ORIGIN=http://localhost:3030
|
||||
NODE_ENV=development
|
||||
|
||||
# OPTIONAL VARIABLES
|
||||
# This is used for validating emails that are allowed to log in. Every email that do not match this regex will be rejected.
|
||||
# WHITELISTED_EMAILS="authorized@yahoo\.com|authorized@gmail\.com"
|
||||
# This is used for logging in via GitHub. You can leave these commented out if you don't want to use GitHub for authentication.
|
||||
# AUTH_GITHUB_CLIENT_ID=
|
||||
# AUTH_GITHUB_CLIENT_SECRET=
|
||||
@@ -31,6 +33,8 @@ CLOUD_AIRTABLE_CLIENT_ID=
|
||||
CLOUD_AIRTABLE_CLIENT_SECRET=
|
||||
CLOUD_GITHUB_CLIENT_ID=
|
||||
CLOUD_GITHUB_CLIENT_SECRET=
|
||||
CLOUD_LINEAR_CLIENT_ID=
|
||||
CLOUD_LINEAR_CLIENT_SECRET=
|
||||
CLOUD_SLACK_APP_HOST=
|
||||
CLOUD_SLACK_CLIENT_ID=
|
||||
CLOUD_SLACK_CLIENT_SECRET=
|
||||
@@ -0,0 +1,38 @@
|
||||
name: 🐞 Bug Report
|
||||
description: Create a bug report to help us improve
|
||||
title: "bug: "
|
||||
labels: ["🐞 unconfirmed bug"]
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Provide environment information
|
||||
description: |
|
||||
Run this command in your project root and paste the results:
|
||||
```bash
|
||||
npx envinfo --system --binaries
|
||||
```
|
||||
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of the bug, as well as what you expected to happen when encountering it.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Reproduction repo
|
||||
description: If applicable, please provide a link to a reproduction repo or a Stackblitz / CodeSandbox project. Your issue may be closed if this is not provided and we are unable to reproduce the issue. If your bug is a docs issue, link the appropriate page.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: To reproduce
|
||||
description: Describe how to reproduce your bug. Steps, code snippets, reproduction repos etc.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Add any other information related to the bug here, screenshots if applicable.
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Ask a Question
|
||||
url: https://trigger.dev/discord
|
||||
about: Ask questions and discuss with other community members
|
||||
@@ -0,0 +1,27 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea for this project
|
||||
title: "feat: "
|
||||
labels: ["🌟 enhancement"]
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like to see
|
||||
description: A clear and concise description of what you want to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe alternate solutions
|
||||
description: A clear and concise description of any alternative solutions or features you've considered.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Add any other information related to the feature here. If your feature request is related to any issues or discussions, link them here.
|
||||
@@ -0,0 +1,12 @@
|
||||
"📌 area: cli":
|
||||
- any: ["cli/**/*"]
|
||||
|
||||
"📌 area: t3-app":
|
||||
- any: ["cli/template/**/*"]
|
||||
|
||||
"📚 documentation":
|
||||
- any: ["www/**/*"]
|
||||
- any: ["**/*.md"]
|
||||
|
||||
"📌 area: ci":
|
||||
- any: [".github/**/*"]
|
||||
@@ -0,0 +1,27 @@
|
||||
Closes #<issue>
|
||||
|
||||
## ✅ Checklist
|
||||
|
||||
- [ ] I have followed every step in the [contributing guide](https://github.com/triggerdotdev/trigger.dev/blob/main/CONTRIBUTING.md)
|
||||
- [ ] The PR title follows the convention.
|
||||
- [ ] I ran and tested the code works
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
_[Describe the steps you took to test this change]_
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
_[Short description of what has changed]_
|
||||
|
||||
---
|
||||
|
||||
## Screenshots
|
||||
|
||||
_[Screenshots]_
|
||||
|
||||
💯
|
||||
@@ -0,0 +1,66 @@
|
||||
name: "🧪 E2E Tests"
|
||||
on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
e2e:
|
||||
name: "🧪 E2E Tests"
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: 🐳 Login to Docker Hub
|
||||
if: github.event_name == 'push'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 7.18
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
- name: 📥 Download deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: |
|
||||
# Setup environment variables
|
||||
cp ./.env.example ./.env
|
||||
cp ./references/nextjs-test/.env.example ./references/nextjs-test/.env.local
|
||||
|
||||
# Build packages
|
||||
pnpm run build --filter @references/nextjs-test^...
|
||||
pnpm --filter @trigger.dev/database generate
|
||||
|
||||
# Move trigger-cli bin to correct place
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
# Execute tests
|
||||
pnpm run docker
|
||||
pnpm run db:migrate
|
||||
pnpm run db:seed
|
||||
pnpm run test:e2e
|
||||
|
||||
# Cleanup
|
||||
pnpm run docker:stop
|
||||
|
||||
- name: Upload Playwright report
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
@@ -0,0 +1,32 @@
|
||||
name: 🤖 PR Checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
- "**.mdx"
|
||||
- ".github/CODEOWNERS"
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
typecheck:
|
||||
uses: ./.github/workflows/typecheck.yml
|
||||
secrets: inherit
|
||||
|
||||
units:
|
||||
uses: ./.github/workflows/unit-tests.yml
|
||||
secrets: inherit
|
||||
|
||||
e2e:
|
||||
uses: ./.github/workflows/e2e.yml
|
||||
secrets: inherit
|
||||
@@ -0,0 +1,72 @@
|
||||
name: "🐳 Publish Docker"
|
||||
on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
outputs:
|
||||
version: ${{ steps.get_version.outputs.version }}
|
||||
short_sha: ${{ steps.get_commit.outputs.sha_short }}
|
||||
steps:
|
||||
- name: 🐳 Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 🆚 Get the version
|
||||
id: get_version
|
||||
run: |
|
||||
IMAGE_TAG="${GITHUB_REF#refs/tags/}"
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
if [[ $IMAGE_TAG == v.docker.* ]]; then
|
||||
ORIGINAL_VERSION="${IMAGE_TAG#v.docker.}"
|
||||
IMAGE_TAG="v${ORIGINAL_VERSION}"
|
||||
elif [[ $IMAGE_TAG == build-* ]]; then
|
||||
IMAGE_TAG="${IMAGE_TAG#build-}"
|
||||
fi
|
||||
echo "IMAGE_TAG=${IMAGE_TAG}"
|
||||
elif [[ $GITHUB_REF == refs/heads/main ]]; then
|
||||
# Handle main branch specifically
|
||||
IMAGE_TAG="main"
|
||||
echo "IMAGE_TAG=${IMAGE_TAG}"
|
||||
else
|
||||
echo "Invalid reference: ${GITHUB_REF}"
|
||||
exit 1
|
||||
fi
|
||||
echo "::set-output name=version::${IMAGE_TAG}"
|
||||
- name: 🔢 Get the commit hash
|
||||
id: get_commit
|
||||
run: |
|
||||
echo ::set-output name=sha_short::$(echo ${{ github.sha }} | cut -c1-7)
|
||||
|
||||
- name: 🐳 Build Docker Image
|
||||
run: |
|
||||
docker build -t release_build_image -f ./docker/Dockerfile .
|
||||
|
||||
- name: 🐙 Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: 🐙 Push to GitHub Container Registry
|
||||
run: |
|
||||
docker tag release_build_image $REGISTRY/$REPOSITORY:$IMAGE_TAG
|
||||
docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG
|
||||
env:
|
||||
REGISTRY: ghcr.io/triggerdotdev
|
||||
REPOSITORY: trigger.dev
|
||||
IMAGE_TAG: ${{ steps.get_version.outputs.version }}
|
||||
|
||||
- name: 🐙 Push 'latest' to GitHub Container Registry
|
||||
if: startsWith(github.ref, 'refs/tags/v.docker')
|
||||
run: |
|
||||
docker tag release_build_image $REGISTRY/$REPOSITORY:latest
|
||||
docker push $REGISTRY/$REPOSITORY:latest
|
||||
env:
|
||||
REGISTRY: ghcr.io/triggerdotdev
|
||||
REPOSITORY: trigger.dev
|
||||
+12
-183
@@ -4,9 +4,9 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- improvements/*
|
||||
tags:
|
||||
- "v.docker.*"
|
||||
- "build-*"
|
||||
paths:
|
||||
- ".github/workflows/publish.yml"
|
||||
- "packages/**"
|
||||
@@ -23,6 +23,7 @@ on:
|
||||
- "turbo.json"
|
||||
- "docker/Dockerfile"
|
||||
- "docker/scripts/**"
|
||||
- "tests/**"
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
@@ -37,190 +38,18 @@ env:
|
||||
|
||||
jobs:
|
||||
typecheck:
|
||||
name: ʦ TypeScript
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
uses: ./.github/workflows/typecheck.yml
|
||||
secrets: inherit
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 7.18
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
- name: 📥 Download deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: 📀 Generate Prisma Client
|
||||
run: pnpm run generate
|
||||
|
||||
- name: 🔎 Type check
|
||||
run: pnpm run typecheck --filter webapp
|
||||
|
||||
unitTests:
|
||||
name: Unit Tests
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 7.18
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
- name: 📥 Download deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: |
|
||||
pnpm run test
|
||||
units:
|
||||
uses: ./.github/workflows/unit-tests.yml
|
||||
secrets: inherit
|
||||
|
||||
e2e:
|
||||
name: e2e Tests
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: 🐳 Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 7.18
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
- name: 📥 Download deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: |
|
||||
# Setup environment variables
|
||||
cp ./.env.example ./.env
|
||||
cp ./examples/nextjs-test/.env.example ./examples/nextjs-test/.env.local
|
||||
|
||||
# Build packages
|
||||
pnpm run build --filter @examples/nextjs-test^...
|
||||
pnpm --filter @trigger.dev/database generate
|
||||
|
||||
# Move trigger-cli bin to correct place
|
||||
pnpm install --frozen-lockfile
|
||||
|
||||
# Execute tests
|
||||
pnpm run docker
|
||||
pnpm run db:migrate
|
||||
pnpm run db:seed
|
||||
pnpm run test:e2e
|
||||
|
||||
# Cleanup
|
||||
pnpm run docker:stop
|
||||
|
||||
- name: Upload Playwright report
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
uses: ./.github/workflows/e2e.yml
|
||||
secrets: inherit
|
||||
|
||||
publish:
|
||||
needs: [typecheck, unitTests, e2e]
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
outputs:
|
||||
version: ${{ steps.get_version.outputs.version }}
|
||||
short_sha: ${{ steps.get_commit.outputs.sha_short }}
|
||||
steps:
|
||||
- name: 🐳 Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: 🆚 Get the version
|
||||
id: get_version
|
||||
run: |
|
||||
IMAGE_TAG="${GITHUB_REF#refs/tags/}"
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
if [[ $IMAGE_TAG == v.docker.* ]]; then
|
||||
ORIGINAL_VERSION="${IMAGE_TAG#v.docker.}"
|
||||
IMAGE_TAG="v${ORIGINAL_VERSION}"
|
||||
fi
|
||||
echo "IMAGE_TAG=${IMAGE_TAG}"
|
||||
elif [[ $GITHUB_REF == refs/heads/improvements/* ]]; then
|
||||
ORIGINAL_VERSION="${GITHUB_REF#refs/heads/improvements/}"
|
||||
IMAGE_TAG="${ORIGINAL_VERSION}.rc"
|
||||
echo "IMAGE_TAG=${IMAGE_TAG}"
|
||||
elif [[ $GITHUB_REF == refs/heads/* ]]; then
|
||||
IMAGE_TAG="${GITHUB_REF#refs/heads/}"
|
||||
echo "IMAGE_TAG=${IMAGE_TAG}"
|
||||
else
|
||||
echo "Invalid reference: ${GITHUB_REF}"
|
||||
exit 1
|
||||
fi
|
||||
echo "::set-output name=version::${IMAGE_TAG}"
|
||||
- name: 🔢 Get the commit hash
|
||||
id: get_commit
|
||||
run: |
|
||||
echo ::set-output name=sha_short::$(echo ${{ github.sha }} | cut -c1-7)
|
||||
|
||||
- name: 🐳 Build Docker Image
|
||||
run: |
|
||||
docker build -t release_build_image -f ./docker/Dockerfile .
|
||||
|
||||
- name: 🐙 Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: 🐙 Push to GitHub Container Registry
|
||||
run: |
|
||||
docker tag release_build_image $REGISTRY/$REPOSITORY:$IMAGE_TAG
|
||||
docker push $REGISTRY/$REPOSITORY:$IMAGE_TAG
|
||||
env:
|
||||
REGISTRY: ghcr.io/triggerdotdev
|
||||
REPOSITORY: trigger.dev
|
||||
IMAGE_TAG: ${{ steps.get_version.outputs.version }}
|
||||
|
||||
- name: 🐙 Push 'latest' to GitHub Container Registry
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
run: |
|
||||
docker tag release_build_image $REGISTRY/$REPOSITORY:latest
|
||||
docker push $REGISTRY/$REPOSITORY:latest
|
||||
env:
|
||||
REGISTRY: ghcr.io/triggerdotdev
|
||||
REPOSITORY: trigger.dev
|
||||
needs: [typecheck, units, e2e]
|
||||
uses: ./.github/workflows/publish-docker.yml
|
||||
secrets: inherit
|
||||
|
||||
@@ -4,16 +4,11 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- ".github/workflows/release.yml"
|
||||
- "packages/**"
|
||||
- "!packages/**/*.md"
|
||||
- "changesets/**"
|
||||
- "integrations/**"
|
||||
- "!integrations/**/*.md"
|
||||
- "pnpm-lock.yaml"
|
||||
- "pnpm-workspace.yaml"
|
||||
- "turbo.json"
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
- ".github/CODEOWNERS"
|
||||
- ".github/ISSUE_TEMPLATE/**"
|
||||
|
||||
|
||||
jobs:
|
||||
release:
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
name: "ʦ TypeScript"
|
||||
on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
typecheck:
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 7.18
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
- name: 📥 Download deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: 📀 Generate Prisma Client
|
||||
run: pnpm run generate
|
||||
|
||||
- name: 🔎 Type check
|
||||
run: pnpm run typecheck --filter webapp
|
||||
@@ -0,0 +1,30 @@
|
||||
name: "🧪 Unit Tests"
|
||||
on:
|
||||
workflow_call:
|
||||
jobs:
|
||||
unitTests:
|
||||
name: "🧪 Unit Tests"
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: ⬇️ Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: ⎔ Setup pnpm
|
||||
uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 7.18
|
||||
|
||||
- name: ⎔ Setup node
|
||||
uses: buildjet/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
- name: 📥 Download deps
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: |
|
||||
pnpm run test
|
||||
@@ -52,3 +52,5 @@ apps/**/public/build
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright/.cache/
|
||||
|
||||
.cosine
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"astro-build.astro-vscode",
|
||||
"denoland.vscode-deno"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
|
||||
]
|
||||
}
|
||||
Vendored
+13
-13
@@ -5,29 +5,29 @@
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "pnpm run dev --filter webapp",
|
||||
"name": "Run webapp",
|
||||
"request": "launch",
|
||||
"type": "node-terminal",
|
||||
"cwd": "${workspaceFolder}"
|
||||
"request": "launch",
|
||||
"name": "Debug WebApp",
|
||||
"command": "pnpm run dev --filter webapp",
|
||||
"envFile": "${workspaceFolder}/apps/webapp/.env",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"sourceMaps": true
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Chrome webapp",
|
||||
"url": "http://localhost:3000",
|
||||
"url": "http://localhost:3030",
|
||||
"webRoot": "${workspaceFolder}/apps/webapp/app"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"type": "node-terminal",
|
||||
"request": "launch",
|
||||
"name": "Debug Current Test File",
|
||||
"autoAttachChildProcesses": true,
|
||||
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
|
||||
"program": "${workspaceRoot}/node_modules/vitest/vitest.mjs",
|
||||
"args": ["run", "${relativeFile}"],
|
||||
"smartStep": true,
|
||||
"console": "integratedTerminal"
|
||||
"name": "Debug BYO Auth",
|
||||
"command": "pnpm run byo-auth",
|
||||
"envFile": "${workspaceFolder}/references/job-catalog/.env",
|
||||
"cwd": "${workspaceFolder}/references/job-catalog",
|
||||
"sourceMaps": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"deno.enablePaths": ["references/deno-reference"],
|
||||
"debug.toolBarLocation": "commandCenter"
|
||||
}
|
||||
@@ -27,3 +27,31 @@ Please follow the best-practice of adding changesets in the same commit as the c
|
||||
3. Create version `pnpm run changeset:version`
|
||||
4. Release `pnpm run changeset:release`
|
||||
5. Switch back to normal mode by running `pnpm run changeset:normal`
|
||||
|
||||
## Snapshot instructions
|
||||
|
||||
!MAKE SURE TO UPDATE THE TAG IN THE INSTRUCTIONS BELOW!
|
||||
|
||||
1. Add changesets as usual
|
||||
|
||||
```sh
|
||||
pnpm run changeset:add
|
||||
```
|
||||
|
||||
2. Create a snapshot version (replace "prerelease" with your tag)
|
||||
|
||||
```sh
|
||||
pnpm exec changeset version --snapshot prerelease
|
||||
```
|
||||
|
||||
3. Build the packages:
|
||||
|
||||
```sh
|
||||
pnpm run build --filter "@trigger.dev/*"
|
||||
```
|
||||
|
||||
4. Publish the snapshot (replace "dev" with your tag)
|
||||
|
||||
```sh
|
||||
pnpm exec changeset publish --no-git-tag --snapshot --tag prerelease
|
||||
```
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
- Other conduct that could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned with this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).
|
||||
+25
-13
@@ -23,11 +23,11 @@ branch are tagged into a release monthly.
|
||||
1. Clone the repo into a public GitHub repository or [fork the repo](https://github.com/triggerdotdev/trigger.dev/fork). If you plan to distribute the code, keep the source code public to comply with the [Apache Licence 2.0](https://github.com/triggerdotdev/trigger.dev/blob/main/LICENSE).
|
||||
|
||||
```
|
||||
git clone https://github.com/triggerdotdev/trigger.dev.git
|
||||
git clone https://github.com/<github_username>/trigger.dev.git
|
||||
```
|
||||
|
||||
> If you are on windows, run the following command on gitbash with admin privileges:
|
||||
> `git clone -c core.symlinks=true https://github.com/triggerdotdev/trigger.dev.git`
|
||||
> `git clone -c core.symlinks=true https://github.com/<github_username>/trigger.dev.git`
|
||||
|
||||
2. Navigate to the project folder
|
||||
```
|
||||
@@ -52,9 +52,13 @@ branch are tagged into a release monthly.
|
||||
Feel free to update `SESSION_SECRET` and `MAGIC_LINK_SECRET` as well using the same method.
|
||||
|
||||
6. Start Docker. This starts the required services like Postgres. If this is your first time using Docker, consider going through this [guide](DOCKER_INSTALLATION.md)
|
||||
|
||||
```
|
||||
pnpm run docker
|
||||
```
|
||||
|
||||
This will also start and run a local instance of [pgAdmin](https://www.pgadmin.org/) on [localhost:5480](http://localhost:5480), preconfigured with email `admin@example.com` and pwd `admin`. Then use `postgres` as the password to the Trigger.dev server.
|
||||
|
||||
7. Migrate the database
|
||||
```
|
||||
pnpm run db:migrate
|
||||
@@ -133,10 +137,10 @@ pnpm run dev
|
||||
|
||||
2. Open a new Terminal window and run the webapp locally and then create a new project in the dashboard. Copy out the dev API key.
|
||||
|
||||
3. Create a new temporary Next.js app in examples directory
|
||||
3. Create a new temporary Next.js app in references directory
|
||||
|
||||
```sh
|
||||
cd ./examples
|
||||
cd ./references
|
||||
pnpm create next-app@latest test-cli --ts --no-eslint --tailwind --app --src-dir --import-alias "@/*"
|
||||
```
|
||||
|
||||
@@ -149,7 +153,7 @@ pnpm create next-app@latest test-cli --ts --no-eslint --tailwind --app --src-dir
|
||||
}
|
||||
```
|
||||
|
||||
5. Back in the terminal, navigate into the example, and initialize the CLI. When prompted, select `self-hosted` and enter `localhost:3030` if you are testing against the local instance of Trigger.dev, or you can just use the Trigger.dev cloud. When asked for an API key, use the key you copied earlier.
|
||||
5. Back in the terminal, navigate into the reference, and initialize the CLI. When prompted, select `self-hosted` and enter `localhost:3030` if you are testing against the local instance of Trigger.dev, or you can just use the Trigger.dev cloud. When asked for an API key, use the key you copied earlier.
|
||||
|
||||
```sh
|
||||
cd ./test-cli
|
||||
@@ -179,14 +183,14 @@ To run the end-to-end tests, follow the steps below:
|
||||
|
||||
```sh
|
||||
cp ./.env.example ./.env
|
||||
cp ./examples/nextjs-test/.env.example ./examples/nextjs-test/.env.local
|
||||
cp ./references/nextjs-test/.env.example ./references/nextjs-test/.env.local
|
||||
```
|
||||
|
||||
2. Set up dependencies
|
||||
|
||||
```sh
|
||||
# Build packages
|
||||
pnpm run build --filter @examples/nextjs-test^...
|
||||
pnpm run build --filter @references/nextjs-test^...
|
||||
pnpm --filter @trigger.dev/database generate
|
||||
|
||||
# Move trigger-cli bin to correct place
|
||||
@@ -221,11 +225,11 @@ pnpm run db:studio
|
||||
|
||||
## Add sample jobs
|
||||
|
||||
The [examples/jobs-starter](./examples/jobs-starter/) project defines simple jobs you can get started with.
|
||||
The [references/job-catalog](./references/job-catalog/) project defines simple jobs you can get started with.
|
||||
|
||||
1. `cd` into `examples/jobs-starter`
|
||||
2. Create a `.env.local` file with the following content,
|
||||
replacing `[TRIGGER_DEV_API_KEY]` with an actual key:
|
||||
1. `cd` into `references/job-catalog`
|
||||
2. Create a `.env` file with the following content,
|
||||
replacing `<TRIGGER_DEV_API_KEY>` with an actual key:
|
||||
|
||||
```env
|
||||
TRIGGER_API_KEY=[TRIGGER_DEV_API_KEY]
|
||||
@@ -235,12 +239,20 @@ TRIGGER_API_URL=http://localhost:3030
|
||||
`TRIGGER_API_URL` is used to configure the URL for your Trigger.dev instance,
|
||||
where the jobs will be registered.
|
||||
|
||||
3. Run the `jobs-starter` app:
|
||||
3. Run one of the the `job-catalog` files:
|
||||
|
||||
```sh
|
||||
pnpm dev
|
||||
pnpm run events
|
||||
```
|
||||
|
||||
This will open up a local server using `express` on port 8080. Then in a new terminal window you can run the trigger-cli dev command:
|
||||
|
||||
```sh
|
||||
pnpm run dev:trigger
|
||||
```
|
||||
|
||||
See the [Job Catalog](./references/job-catalog/README.md) file for more.
|
||||
|
||||
4. Navigate to your trigger.dev instance ([http://localhost:3030](http://localhost:3030/)), to see the jobs.
|
||||
You can use the test feature to trigger them.
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<img alt="Trigger.dev logo" src="https://imagedelivery.net/3TbraffuDZ4aEf8KWOmI_w/a45d1fa2-0ae8-4a39-4409-f4f934bfae00/public">
|
||||
</picture>
|
||||
|
||||
### The background jobs framework for Next.js
|
||||
### The open source background jobs framework
|
||||
|
||||
[Discord](https://discord.gg/JtBAxBr2m3) | [Website](https://trigger.dev) | [Issues](https://github.com/triggerdotdev/trigger.dev/issues) | [Docs](https://trigger.dev/docs)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
Create long-running jobs directly in your codebase with features like API integrations, webhooks, scheduling and delays.
|
||||
|
||||
### Long running Jobs on serverless
|
||||
## Long running Jobs on serverless
|
||||
|
||||
Reliably run jobs and don’t worry about function timeouts, we handle those for you.
|
||||
|
||||
@@ -26,31 +26,51 @@ Reliably run jobs and don’t worry about function timeouts, we handle those for
|
||||
- Auto-resume after a server outage
|
||||
- Add delays of up to a year
|
||||
|
||||
### In your codebase
|
||||
## In your codebase
|
||||
|
||||
Create Jobs where they belong: in your codebase. Version control, localhost, test, review, and deploy like you're already used to.
|
||||
|
||||
### Secure by design
|
||||
## Secure by design
|
||||
|
||||
We only receive Triggers and the data you choose to send to us. You can even completely self-host the entire platform.
|
||||
|
||||
### Don't worry about deployment
|
||||
## Don't worry about deployment
|
||||
|
||||
Just use our SDK to write Jobs in your Next.js codebase. There's nothing extra to deploy and no CI to configure, your Jobs just connect to our cloud. Or you can always self-host.
|
||||
Just use our SDK to write Jobs in your codebase. There's nothing extra to deploy and no CI to configure, your Jobs just connect to our cloud. Or you can always self-host.
|
||||
|
||||
### Full visibility of every job run
|
||||
## Full visibility of every job run
|
||||
|
||||
View every Task in every Run so you can tell exactly what happened.
|
||||
|
||||

|
||||
|
||||
### Built-in integrations
|
||||
## Built-in integrations
|
||||
|
||||
Easily integrate with hundreds of third-party APIs – including your own. Use API keys (which never leave your server) or let us handle OAuth for you. Install our integration packages and easily subscribe to webhooks and perform common tasks, or you can easily use your existing favorite Node.JS SDKs and get resumability and idempotency through our `runTask` function.
|
||||
|
||||
### Trigger.dev Connect (coming soon)
|
||||
## Our progress
|
||||
|
||||
Easily add integrations for your users.
|
||||
We’re building the most comprehensive and easy-to-use background jobs framework for developers.
|
||||
|
||||
Click the links to join the discussions about our upcoming features.
|
||||
|
||||
| Feature | What it does | Status |
|
||||
| ------------------------------------------------------------------------------------ | --------------------------------------------------- | ------ |
|
||||
| Integration kit | Official Trigger.dev integrations or build your own | ✅ |
|
||||
| Self-hosting | Host the platform yourself | ✅ |
|
||||
| Cloud | Just write code, no deployment required | ✅ |
|
||||
| Dashboard | View every Task in every Run | ✅ |
|
||||
| Serverless | Long-running Jobs on your serverless backend | ✅ |
|
||||
| React hooks | Easily update your UI with Job progress | ✅ |
|
||||
| React frameworks | Support for Remix, Astro, RedwoodJS & more | ✅ |
|
||||
| [Background tasks](https://github.com/triggerdotdev/trigger.dev/discussions/400) | Offload long or intense Tasks to our infrastructure | 🛠️ |
|
||||
| [Long-running servers](https://github.com/triggerdotdev/trigger.dev/discussions/430) | Run Jobs on your long-running backend | 🛠️ |
|
||||
| Polling Triggers | Subscribe to changes without webhooks | 🕝 |
|
||||
| Vercel integration | Easy deploy and preview environment support | 🕝 |
|
||||
| Streaming | Receive data from your Jobs in realtime | 🕝 |
|
||||
| 100+ integrations | Comprehensive support for popular APIs | 🕝 |
|
||||
| [Trigger.dev Connect](https://github.com/triggerdotdev/trigger.dev/discussions/441) | Use integrations signed in as your users | 🕝 |
|
||||
| File IO | Create Tasks that have file outputs | 🕝 |
|
||||
|
||||
# Getting started
|
||||
|
||||
@@ -63,3 +83,9 @@ We provide an official trigger.dev docker image you can use to easily self-host
|
||||
## Development
|
||||
|
||||
To setup and develop locally or contribute to the open source project, follow our [development guide](./CONTRIBUTING.md).
|
||||
|
||||
## Meet the Amazing People Behind This Project 🚀
|
||||
|
||||
<a href="https://github.com/triggerdotdev/trigger.dev/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=triggerdotdev/trigger.dev" />
|
||||
</a>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
REWRITE_HOSTNAME=
|
||||
AWS_SQS_ACCESS_KEY_ID=
|
||||
AWS_SQS_SECRET_ACCESS_KEY=
|
||||
AWS_SQS_QUEUE_URL=
|
||||
AWS_SQS_REGION=
|
||||
#optional
|
||||
#REWRITE_PORT=
|
||||
@@ -0,0 +1,13 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
@@ -0,0 +1,172 @@
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
\*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
\*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
\*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
\*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
.cache/
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.\*
|
||||
|
||||
# wrangler project
|
||||
|
||||
.dev.vars
|
||||
.wrangler/
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"jsxSingleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"bracketSpacing": true,
|
||||
"bracketSameLine": false,
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# proxy
|
||||
|
||||
## 0.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [067e19fe]
|
||||
- @trigger.dev/core@2.2.8
|
||||
|
||||
## 0.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [756024da]
|
||||
- @trigger.dev/core@2.2.7
|
||||
@@ -0,0 +1,68 @@
|
||||
# Trigger.dev proxy
|
||||
|
||||
This is an optional module that can be used to proxy and queue requests to the Trigger.dev API.
|
||||
|
||||
## Why?
|
||||
|
||||
The Trigger.dev API is designed to be fast and reliable. However, if you have a lot of traffic, you may want to use this proxy to queue requests to the API. It intercepts some requests to the API and adds them to an AWS SQS queue, then the webapp can be setup to process the queue.
|
||||
|
||||
## Current features
|
||||
|
||||
- Intercepts `sendEvent` requests and adds them to an AWS SQS queue. The webapp then reads from the queue and creates the events.
|
||||
|
||||
## Setup
|
||||
|
||||
### Create an AWS SQS queue
|
||||
|
||||
In AWS you should create a new AWS SQS queue with appropriate security settings. You will need the queue URL for the next step.
|
||||
|
||||
### Environment variables
|
||||
|
||||
#### Cloudflare secrets
|
||||
|
||||
Locally you should copy the `.dev.var.example` file to `.dev.var` and fill in the values.
|
||||
|
||||
When deploying you should use `wrangler` (the Cloudflare CLI tool) to set secrets. Make sure you set the correct --env ("staging" or "prod")
|
||||
|
||||
```bash
|
||||
wrangler secret put REWRITE_HOSTNAME --env staging
|
||||
wrangler secret put AWS_SQS_ACCESS_KEY_ID --env staging
|
||||
wrangler secret put AWS_SQS_SECRET_ACCESS_KEY --env staging
|
||||
wrangler secret put AWS_SQS_QUEUE_URL --env staging
|
||||
wrangler secret put AWS_SQS_REGION --env staging
|
||||
```
|
||||
|
||||
You need to set your API CNAME entry to be proxied by Cloudflare. You can do this in the Cloudflare dashboard.
|
||||
|
||||
#### Webapp
|
||||
|
||||
These env vars also need setting in the webapp.
|
||||
|
||||
```bash
|
||||
AWS_SQS_REGION
|
||||
AWS_SQS_ACCESS_KEY_ID
|
||||
AWS_SQS_SECRET_ACCESS_KEY
|
||||
AWS_SQS_QUEUE_URL
|
||||
AWS_SQS_BATCH_SIZE
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
Staging:
|
||||
|
||||
```bash
|
||||
npx wrangler@latest deploy --route "<your-api-subdomain>/*" --env staging
|
||||
```
|
||||
|
||||
Prod:
|
||||
|
||||
```bash
|
||||
npx wrangler@latest deploy --route "<your-api-subdomain>/*" --env prod
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Set the environment variables as described above.
|
||||
|
||||
1. `pnpm install`
|
||||
2. `pnpm run dev --filter proxy`
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "proxy",
|
||||
"version": "0.0.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"deploy": "wrangler deploy",
|
||||
"dev": "wrangler dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20230419.0",
|
||||
"typescript": "^5.0.4",
|
||||
"wrangler": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-sqs": "^3.445.0",
|
||||
"@trigger.dev/core": "workspace:*",
|
||||
"ulidx": "^2.2.1",
|
||||
"zod": "3.22.3",
|
||||
"zod-error": "1.5.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const AuthorizationHeaderSchema = z.string().regex(/^Bearer .+$/);
|
||||
|
||||
export function getApiKeyFromRequest(request: Request) {
|
||||
const rawAuthorization = request.headers.get("Authorization");
|
||||
|
||||
const authorization = AuthorizationHeaderSchema.safeParse(rawAuthorization);
|
||||
if (!authorization.success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const apiKey = authorization.data.replace(/^Bearer /, "");
|
||||
const type = isPrivateApiKey(apiKey) ? ("PRIVATE" as const) : ("PUBLIC" as const);
|
||||
return { apiKey, type };
|
||||
}
|
||||
|
||||
function isPrivateApiKey(key: string) {
|
||||
return key.startsWith("tr_");
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
|
||||
import { ApiEventLog, SendEventBodySchema } from "@trigger.dev/core";
|
||||
import { generateErrorMessage } from "zod-error";
|
||||
import { Env } from "..";
|
||||
import { getApiKeyFromRequest } from "../apikey";
|
||||
import { json } from "../json";
|
||||
import { calculateDeliverAt } from "./utils";
|
||||
|
||||
/** Adds the event to an AWS SQS queue, so it can be consumed from the main Trigger.dev API */
|
||||
export async function queueEvent(request: Request, env: Env): Promise<Response> {
|
||||
//check there's a private API key
|
||||
const apiKeyResult = getApiKeyFromRequest(request);
|
||||
if (!apiKeyResult || apiKeyResult.type !== "PRIVATE") {
|
||||
return json(
|
||||
{ error: "Invalid or Missing API key" },
|
||||
{
|
||||
status: 401,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//parse the request body
|
||||
try {
|
||||
const anyBody = await request.json();
|
||||
const body = SendEventBodySchema.safeParse(anyBody);
|
||||
if (!body.success) {
|
||||
return json(
|
||||
{ error: generateErrorMessage(body.error.issues) },
|
||||
{
|
||||
status: 422,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// The AWS SDK tries to use crypto from off of the window,
|
||||
// so we need to trick it into finding it where it expects it
|
||||
globalThis.global = globalThis;
|
||||
|
||||
const client = new SQSClient({
|
||||
region: env.AWS_SQS_REGION,
|
||||
credentials: {
|
||||
accessKeyId: env.AWS_SQS_ACCESS_KEY_ID,
|
||||
secretAccessKey: env.AWS_SQS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
const timestamp = body.data.event.timestamp ?? new Date();
|
||||
|
||||
//add the event to the queue
|
||||
const send = new SendMessageCommand({
|
||||
// use wrangler secrets to provide this global variable
|
||||
QueueUrl: env.AWS_SQS_QUEUE_URL,
|
||||
MessageBody: JSON.stringify({
|
||||
event: { ...body.data.event, timestamp },
|
||||
options: body.data.options,
|
||||
apiKey: apiKeyResult.apiKey,
|
||||
}),
|
||||
});
|
||||
|
||||
const queuedEvent = await client.send(send);
|
||||
console.log("Queued event", queuedEvent);
|
||||
|
||||
//respond with the event
|
||||
const event: ApiEventLog = {
|
||||
id: body.data.event.id,
|
||||
name: body.data.event.name,
|
||||
payload: body.data.event.payload,
|
||||
context: body.data.event.context,
|
||||
timestamp,
|
||||
deliverAt: calculateDeliverAt(body.data.options),
|
||||
};
|
||||
|
||||
return json(event, {
|
||||
status: 200,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("queueEvent error", e);
|
||||
return json(
|
||||
{
|
||||
error: `Failed to send event: ${e instanceof Error ? e.message : JSON.stringify(e)}`,
|
||||
},
|
||||
{
|
||||
status: 422,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { SQSClient, SendMessageBatchCommand } from "@aws-sdk/client-sqs";
|
||||
import { ApiEventLog, SendBulkEventsBodySchema } from "@trigger.dev/core";
|
||||
import { generateErrorMessage } from "zod-error";
|
||||
import { Env } from "..";
|
||||
import { getApiKeyFromRequest } from "../apikey";
|
||||
import { json } from "../json";
|
||||
import { calculateDeliverAt } from "./utils";
|
||||
|
||||
/** Adds the event to an AWS SQS queue, so it can be consumed from the main Trigger.dev API */
|
||||
export async function queueEvents(request: Request, env: Env): Promise<Response> {
|
||||
//check there's a private API key
|
||||
const apiKeyResult = getApiKeyFromRequest(request);
|
||||
if (!apiKeyResult || apiKeyResult.type !== "PRIVATE") {
|
||||
return json(
|
||||
{ error: "Invalid or Missing API key" },
|
||||
{
|
||||
status: 401,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//parse the request body
|
||||
try {
|
||||
const anyBody = await request.json();
|
||||
const body = SendBulkEventsBodySchema.safeParse(anyBody);
|
||||
if (!body.success) {
|
||||
return json(
|
||||
{ error: generateErrorMessage(body.error.issues) },
|
||||
{
|
||||
status: 422,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// The AWS SDK tries to use crypto from off of the window,
|
||||
// so we need to trick it into finding it where it expects it
|
||||
globalThis.global = globalThis;
|
||||
|
||||
const client = new SQSClient({
|
||||
region: env.AWS_SQS_REGION,
|
||||
credentials: {
|
||||
accessKeyId: env.AWS_SQS_ACCESS_KEY_ID,
|
||||
secretAccessKey: env.AWS_SQS_SECRET_ACCESS_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
const updatedEvents: ApiEventLog[] = body.data.events.map((event) => {
|
||||
const timestamp = event.timestamp ?? new Date();
|
||||
return {
|
||||
...event,
|
||||
payload: event.payload,
|
||||
timestamp,
|
||||
};
|
||||
});
|
||||
|
||||
//divide updatedEvents into multiple batches of 10 (max size SQS accepts)
|
||||
const batches: ApiEventLog[][] = [];
|
||||
let currentBatch: ApiEventLog[] = [];
|
||||
for (let i = 0; i < updatedEvents.length; i++) {
|
||||
currentBatch.push(updatedEvents[i]);
|
||||
if (currentBatch.length === 10) {
|
||||
batches.push(currentBatch);
|
||||
currentBatch = [];
|
||||
}
|
||||
}
|
||||
if (currentBatch.length > 0) {
|
||||
batches.push(currentBatch);
|
||||
}
|
||||
|
||||
//loop through the batches and send them
|
||||
for (let i = 0; i < batches.length; i++) {
|
||||
const batch = batches[i];
|
||||
//add the event to the queue
|
||||
const send = new SendMessageBatchCommand({
|
||||
// use wrangler secrets to provide this global variable
|
||||
QueueUrl: env.AWS_SQS_QUEUE_URL,
|
||||
Entries: batch.map((event, index) => ({
|
||||
Id: `event-${index}`,
|
||||
MessageBody: JSON.stringify({
|
||||
event,
|
||||
options: body.data.options,
|
||||
apiKey: apiKeyResult.apiKey,
|
||||
}),
|
||||
})),
|
||||
});
|
||||
|
||||
const queuedEvent = await client.send(send);
|
||||
console.log("Queued events", queuedEvent);
|
||||
}
|
||||
|
||||
//respond with the events
|
||||
const events: ApiEventLog[] = updatedEvents.map((event) => ({
|
||||
...event,
|
||||
payload: event.payload,
|
||||
deliverAt: calculateDeliverAt(body.data.options),
|
||||
}));
|
||||
|
||||
return json(events, {
|
||||
status: 200,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("queueEvents error", e);
|
||||
return json(
|
||||
{
|
||||
error: `Failed to send events: ${e instanceof Error ? e.message : JSON.stringify(e)}`,
|
||||
},
|
||||
{
|
||||
status: 422,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { SendEventOptions } from "@trigger.dev/core";
|
||||
|
||||
export function calculateDeliverAt(options?: SendEventOptions) {
|
||||
// If deliverAt is a string and a valid date, convert it to a Date object
|
||||
if (options?.deliverAt) {
|
||||
return options?.deliverAt;
|
||||
}
|
||||
|
||||
// deliverAfter is the number of seconds to wait before delivering the event
|
||||
if (options?.deliverAfter) {
|
||||
return new Date(Date.now() + options.deliverAfter * 1000);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { queueEvent } from "./events/queueEvent";
|
||||
import { queueEvents } from "./events/queueEvents";
|
||||
|
||||
export interface Env {
|
||||
/** The hostname needs to be changed to allow requests to pass to the Trigger.dev platform */
|
||||
REWRITE_HOSTNAME: string;
|
||||
REWRITE_PORT?: string;
|
||||
AWS_SQS_ACCESS_KEY_ID: string;
|
||||
AWS_SQS_SECRET_ACCESS_KEY: string;
|
||||
AWS_SQS_QUEUE_URL: string;
|
||||
AWS_SQS_REGION: string;
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
||||
if (!env.REWRITE_HOSTNAME) throw new Error("Missing REWRITE_HOSTNAME");
|
||||
console.log("url", request.url);
|
||||
|
||||
if (!queueingIsEnabled(env)) {
|
||||
console.log("Missing AWS credentials. Passing through to the origin.");
|
||||
return redirectToOrigin(request, env);
|
||||
}
|
||||
|
||||
const url = new URL(request.url);
|
||||
switch (url.pathname) {
|
||||
case "/api/v1/events": {
|
||||
if (request.method === "POST") {
|
||||
return queueEvent(request, env);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "/api/v1/events/bulk": {
|
||||
if (request.method === "POST") {
|
||||
return queueEvents(request, env);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//the same request but with the hostname (and port) changed
|
||||
return redirectToOrigin(request, env);
|
||||
},
|
||||
};
|
||||
|
||||
function redirectToOrigin(request: Request, env: Env) {
|
||||
const newUrl = new URL(request.url);
|
||||
newUrl.hostname = env.REWRITE_HOSTNAME;
|
||||
newUrl.port = env.REWRITE_PORT || newUrl.port;
|
||||
|
||||
const requestInit: RequestInit = {
|
||||
method: request.method,
|
||||
headers: request.headers,
|
||||
body: request.body,
|
||||
};
|
||||
|
||||
console.log("rewritten url", newUrl.toString());
|
||||
return fetch(newUrl.toString(), requestInit);
|
||||
}
|
||||
|
||||
function queueingIsEnabled(env: Env) {
|
||||
return (
|
||||
env.AWS_SQS_ACCESS_KEY_ID &&
|
||||
env.AWS_SQS_SECRET_ACCESS_KEY &&
|
||||
env.AWS_SQS_QUEUE_URL &&
|
||||
env.AWS_SQS_REGION
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export function json(body: any, init?: ResponseInit) {
|
||||
const headers = {
|
||||
"content-type": "application/json",
|
||||
...(init?.headers ?? {}),
|
||||
};
|
||||
|
||||
const responseInit: ResponseInit = {
|
||||
...(init ?? {}),
|
||||
headers,
|
||||
};
|
||||
|
||||
return new Response(JSON.stringify(body), responseInit);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||
"lib": [
|
||||
"es2021"
|
||||
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
|
||||
"jsx": "react" /* Specify what JSX code is generated. */,
|
||||
|
||||
"module": "es2022" /* Specify what module code is generated. */,
|
||||
"moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */,
|
||||
|
||||
"types": [
|
||||
"@cloudflare/workers-types"
|
||||
] /* Specify type package names to be included without being referenced in a source file. */,
|
||||
"resolveJsonModule": true /* Enable importing .json files */,
|
||||
|
||||
"allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */,
|
||||
"checkJs": false /* Enable error reporting in type-checked JavaScript files. */,
|
||||
|
||||
"noEmit": true /* Disable emitting files from a compilation. */,
|
||||
|
||||
"isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */,
|
||||
"allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */,
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@trigger.dev/core": ["../../packages/core/src/index"],
|
||||
"@trigger.dev/core/*": ["../../packages/core/src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
name = "proxy"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2023-10-30"
|
||||
compatibility_flags = [ "nodejs_compat" ]
|
||||
|
||||
[env.staging]
|
||||
[env.prod]
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Preview } from "@storybook/react";
|
||||
import "../app/tailwind.css";
|
||||
import { unstable_createRemixStub } from "@remix-run/testing";
|
||||
import { createRemixStub } from "@remix-run/testing";
|
||||
import React from "react";
|
||||
import { LocaleContextProvider } from "../app/components/primitives/LocaleProvider";
|
||||
import { OperatingSystemContextProvider } from "../app/components/primitives/OperatingSystemProvider";
|
||||
@@ -23,13 +23,16 @@ const preview: Preview = {
|
||||
},
|
||||
],
|
||||
},
|
||||
layout: "fullscreen",
|
||||
},
|
||||
decorators: [
|
||||
(Story) => {
|
||||
const RemixStub = unstable_createRemixStub([
|
||||
const RemixStub = createRemixStub([
|
||||
{
|
||||
path: "/*",
|
||||
element: <Story />,
|
||||
action: () => ({ redirect: "/" }),
|
||||
loader: () => ({ redirect: "/" }),
|
||||
Component: Story,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
export function EndpointIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M8.24336 5.11182C8.75159 4.60359 9.57559 4.60359 10.0838 5.11182L18.8882 13.9162C19.3964 14.4244 19.3964 15.2484 18.8882 15.7566L17.1469 17.4979C16.453 18.2196 15.6222 18.7956 14.7031 19.1922C13.7839 19.5889 12.7949 19.7982 11.7939 19.8081C10.7929 19.8179 9.79992 19.628 8.87318 19.2494C7.94643 18.8708 7.10449 18.3113 6.39662 17.6034C5.68875 16.8955 5.12916 16.0536 4.7506 15.1268C4.37205 14.2001 4.18212 13.2071 4.19194 12.2061C4.20176 11.2051 4.41112 10.2161 4.80779 9.29691C5.20445 8.37777 5.78044 7.54697 6.50206 6.85312L8.24336 5.11182Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M3.32398 20.676L7.11974 16.8803"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.16549"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M15.2535 3.32398L11.4578 7.11974"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.16549"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M20.676 8.74649L16.8803 12.5423"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.16549"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { LogoIcon } from "~/components/LogoIcon";
|
||||
|
||||
export function IntegrationIcon() {
|
||||
return <LogoIcon className="h-3.5 w-3.5 flex-none pb-0.5" />;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
export function RunsIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.2964 3.66962C11.97 3.83662 11.6961 4.09048 11.5047 4.40323C11.3134 4.71598 11.2121 5.07547 11.212 5.44211L11.211 8.0162L9.22047 8.0172C8.95637 8.0172 8.70309 8.12211 8.51635 8.30886C8.3296 8.4956 8.22469 8.74888 8.22469 9.01298V14.9877L8.23166 15.1042C8.26019 15.3464 8.37664 15.5697 8.5589 15.7318C8.74116 15.8939 8.97657 15.9834 9.22047 15.9834L11.211 15.9825L11.212 18.5585C11.2121 18.9524 11.329 19.3373 11.5478 19.6648C11.7666 19.9922 12.0777 20.2474 12.4415 20.3981C12.8054 20.5488 13.2057 20.5882 13.592 20.5114C13.9783 20.4346 14.3331 20.245 14.6116 19.9666L21.1698 13.4084C21.5432 13.0349 21.7529 12.5284 21.7529 12.0003C21.7529 11.4722 21.5432 10.9658 21.1698 10.5923L14.6116 4.03408C14.3331 3.75539 13.9782 3.56558 13.5918 3.48865C13.2054 3.41172 12.8048 3.45114 12.4408 3.60191L12.2964 3.66962Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M3.24578 8.0172C3.48968 8.01723 3.72509 8.10677 3.90735 8.26885C4.08961 8.43092 4.20606 8.65425 4.23459 8.89647L4.24156 9.01298V14.9877C4.24128 15.2415 4.1441 15.4856 3.96986 15.6701C3.79563 15.8547 3.55751 15.9658 3.30414 15.9806C3.05077 15.9955 2.80128 15.9131 2.60666 15.7502C2.41203 15.5873 2.28695 15.3562 2.25697 15.1042L2.25 14.9877V9.01298C2.25 8.74888 2.35491 8.4956 2.54166 8.30885C2.7284 8.12211 2.98168 8.0172 3.24578 8.0172Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
d="M6.23312 8.0172C6.47702 8.01723 6.71243 8.10677 6.89469 8.26885C7.07696 8.43092 7.1934 8.65425 7.22193 8.89647L7.2289 9.01298V14.9877C7.22862 15.2415 7.13144 15.4856 6.95721 15.6701C6.78298 15.8547 6.54485 15.9658 6.29148 15.9806C6.03811 15.9955 5.78863 15.9131 5.594 15.7502C5.39937 15.5873 5.27429 15.3562 5.24431 15.1042L5.23734 14.9877V9.01298C5.23734 8.74888 5.34226 8.4956 5.529 8.30885C5.71575 8.12211 5.96903 8.0172 6.23312 8.0172Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 151 KiB |
@@ -0,0 +1,57 @@
|
||||
export function AstroLogo({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 184 49" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_5336_22429)">
|
||||
<mask id="mask0_5336_22429" maskUnits="userSpaceOnUse" x="0" y="0" width="184" height="49">
|
||||
<path d="M184 0H0V48.4533H184V0Z" fill="white" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_5336_22429)">
|
||||
<path
|
||||
d="M12.4424 40.9986C10.2625 39.0108 9.62608 34.8341 10.5343 31.8083C12.1092 33.716 14.2912 34.3203 16.5514 34.6615C20.0406 35.1879 23.4674 34.991 26.7087 33.4002C27.0795 33.218 27.4221 32.9759 27.8273 32.7306C28.1315 33.6107 28.2106 34.4993 28.1044 35.4037C27.8461 37.6063 26.7472 39.3077 24.9995 40.5974C24.3006 41.1133 23.5611 41.5744 22.8393 42.0609C20.6218 43.5559 20.0219 45.3089 20.8551 47.8589C20.8749 47.921 20.8926 47.9831 20.9374 48.1347C19.8053 47.6293 18.9783 46.8934 18.3481 45.9258C17.6825 44.9046 17.3659 43.7749 17.3492 42.5525C17.3409 41.9577 17.3409 41.3576 17.2607 40.7711C17.0649 39.3413 16.3921 38.7013 15.1245 38.6644C13.8236 38.6265 12.7945 39.4287 12.5216 40.6921C12.5008 40.789 12.4706 40.8849 12.4404 40.9975L12.4424 40.9986Z"
|
||||
fill="url(#paint0_linear_5336_22429)"
|
||||
/>
|
||||
<path
|
||||
d="M0 31.3041C0 31.3041 6.45527 28.1673 12.9286 28.1673L17.8093 13.1001C17.992 12.3714 18.5256 11.8762 19.1278 11.8762C19.7302 11.8762 20.2637 12.3714 20.4464 13.1001L25.3271 28.1673C32.9938 28.1673 38.2557 31.3041 38.2557 31.3041C38.2557 31.3041 27.2909 1.50808 27.2694 1.44829C26.9547 0.567361 26.4234 0 25.7072 0H12.5496C11.8333 0 11.3235 0.567361 10.9874 1.44829C10.9637 1.50695 0 31.3041 0 31.3041Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M68.0598 26.9653C68.0598 29.6056 64.7674 31.1825 60.2089 31.1825C57.2422 31.1825 56.1929 30.4491 56.1929 28.9089C56.1929 27.2954 57.4954 26.5253 60.4622 26.5253C63.1396 26.5253 65.4188 26.5619 68.0598 26.892V26.9653ZM68.096 23.7016C66.4682 23.3349 64.0081 23.1149 61.0773 23.1149C52.5388 23.1149 48.5228 25.1318 48.5228 29.8257C48.5228 34.7029 51.2725 36.5731 57.6402 36.5731C63.031 36.5731 66.6853 35.2163 68.0236 31.8792H68.2407C68.2045 32.6859 68.1683 33.4927 68.1683 34.1161C68.1683 35.8396 68.4578 35.9864 69.8691 35.9864H76.5262C76.1644 34.9596 75.9472 32.0626 75.9472 29.5689C75.9472 26.892 76.0558 24.8751 76.0558 22.1615C76.0558 16.6241 72.7272 13.1037 62.3073 13.1037C57.821 13.1037 52.8282 13.8738 49.0293 15.0106C49.3911 16.5141 49.8977 19.5578 50.1509 21.538C53.4433 19.9979 58.1105 19.3378 61.7283 19.3378C66.7215 19.3378 68.096 20.4745 68.096 22.7848V23.7016Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M86.3622 28.5055C85.4576 28.6155 84.2278 28.6155 82.9613 28.6155C81.6224 28.6155 80.3927 28.5788 79.5604 28.4688C79.5604 28.7622 79.5242 29.0922 79.5242 29.3856C79.5242 33.9695 82.5271 36.6464 93.0917 36.6464C103.041 36.6464 106.261 34.0062 106.261 29.3489C106.261 24.9484 104.127 22.7848 94.6833 22.3081C87.3393 21.9781 86.6879 21.1713 86.6879 20.2545C86.6879 19.1911 87.6288 18.641 92.5489 18.641C97.6506 18.641 99.0251 19.3378 99.0251 20.8046V21.1346C99.7489 21.098 101.052 21.0613 102.39 21.0613C103.656 21.0613 105.031 21.098 105.827 21.1713C105.827 20.8413 105.863 20.5479 105.863 20.2912C105.863 14.9006 101.377 13.1404 92.6937 13.1404C82.9251 13.1404 79.6327 15.524 79.6327 20.1812C79.6327 24.3617 82.2738 26.9653 91.6443 27.3687C98.5547 27.5887 99.3146 28.3588 99.3146 29.4223C99.3146 30.559 98.1928 31.0725 93.345 31.0725C87.7735 31.0725 86.3622 30.3024 86.3622 28.7255V28.5055Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M118.203 9.17995C115.562 11.6369 110.822 14.0939 108.181 14.7539C108.217 16.1107 108.217 18.6044 108.217 19.9612L110.641 19.9979C110.605 22.6015 110.569 25.7552 110.569 27.8454C110.569 32.7226 113.137 36.3897 121.133 36.3897C124.498 36.3897 126.741 36.023 129.527 35.4363C129.237 33.6394 128.912 30.8891 128.804 28.7988C127.139 29.3489 125.041 29.6423 122.725 29.6423C119.505 29.6423 118.203 28.7622 118.203 26.2319C118.203 24.0316 118.203 21.9781 118.239 20.0712C122.364 20.1078 126.488 20.1812 128.912 20.2545C128.876 18.3477 128.948 15.5973 129.057 13.7638C125.547 13.8371 121.604 13.8738 118.348 13.8738C118.384 12.2603 118.42 10.7201 118.456 9.17995H118.203Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M139.812 18.861C139.848 16.9541 139.884 15.3406 139.92 13.7638H132.648C132.757 16.9175 132.757 20.1445 132.757 24.8751C132.757 29.6056 132.72 32.8693 132.648 35.9864H140.969C140.824 33.7861 140.788 30.0823 140.788 26.9286C140.788 21.9414 142.815 20.5112 147.409 20.5112C149.544 20.5112 151.063 20.768 152.402 21.2446C152.438 19.3745 152.8 15.744 153.017 14.1305C151.642 13.7271 150.123 13.4705 148.278 13.4705C144.334 13.4338 141.44 15.0473 140.101 18.8977L139.812 18.861Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M176.038 24.7284C176.038 28.7255 173.143 30.5957 168.584 30.5957C164.062 30.5957 161.167 28.8355 161.167 24.7284C161.167 20.6213 164.098 19.0811 168.584 19.0811C173.107 19.0811 176.038 20.7313 176.038 24.7284ZM183.599 24.5451C183.599 16.5875 177.376 13.0304 168.584 13.0304C159.757 13.0304 153.75 16.5875 153.75 24.5451C153.75 32.4659 159.359 36.7198 168.548 36.7198C177.81 36.7198 183.599 32.4659 183.599 24.5451Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_5336_22429"
|
||||
x1="10.1338"
|
||||
y1="48.1347"
|
||||
x2="31.1641"
|
||||
y2="38.1736"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#D83333" />
|
||||
<stop offset="1" stopColor="#F041FF" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_5336_22429">
|
||||
<rect width="184" height="48.4533" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,45 @@
|
||||
export function FastifyLogo({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 184 55" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_5342_22458)">
|
||||
<path
|
||||
d="M181.319 7.73806L184 0.829078L183.889 0.386903L157.47 7.35116C160.288 3.26104 159.321 0 159.321 0C159.321 0 150.892 5.38901 144.508 5.25083C138.124 5.11265 136.079 3.39922 126.296 6.52208C116.513 9.64494 113.749 19.207 110.903 21.2797C108.084 23.3247 99.213 30.1508 99.213 30.1508L107.227 27.5807C107.227 27.5807 104.961 29.7363 100.291 36.0096V36.0372C101.037 37.0874 104.326 41.4263 107.587 40.4866C107.946 40.3761 108.36 40.1826 108.83 39.9615C110.295 40.7906 112.257 41.5921 114.385 41.8132C114.385 41.8132 112.948 40.155 111.732 38.2205L112.727 37.5849L112.561 37.6954L115.629 38.8008L115.297 35.9267H115.325L118.309 37.0321L117.95 34.4067L119.083 33.854L122.206 22.0258L135.14 13.1823L134.117 15.7801C131.492 22.2193 126.573 23.7393 126.573 23.7393L124.527 24.5407C122.98 26.337 122.344 26.7792 121.819 32.8591C123.063 32.5275 124.223 32.4722 125.301 32.7486C130.828 34.2409 132.735 40.9012 131.243 42.7528C130.884 43.195 129.999 43.9964 128.866 44.9084H126.628L126.6 46.7324L126.379 46.9258H124.085L124.058 48.6945L123.45 49.1367C121.322 49.1919 118.586 47.3127 118.586 47.3127C118.613 49.0261 120.023 51.6515 120.023 51.6515L120.272 51.5134L120.05 51.6792C120.05 51.6792 125.799 55.4929 129.419 54.0835C132.625 52.8123 140.943 46.2625 148.129 43.1673L169.85 37.419L172.725 30.0126L156.171 34.3791V27.6912L175.599 22.5786L178.473 15.1445L156.171 21.0309V14.3707L181.319 7.73806ZM141.883 21.86L147.051 20.5059L147.106 20.7546L145.503 24.9276L140.169 26.337L141.883 21.86ZM143.652 30.7864L138.318 32.1959L140.059 27.7188L145.199 26.3647L145.282 26.6134L143.652 30.7864ZM150.616 29.2941L145.282 30.7035L147.023 26.2265L152.163 24.8723L152.246 25.1211L150.616 29.2941Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M4.00721 16.7197L4.17302 15.863C4.64284 13.3758 5.58246 11.2478 7.32352 9.64495C8.59477 8.4566 10.474 7.73807 12.602 7.73807C13.9561 7.73807 14.9787 7.93152 15.6143 8.15261L14.426 12.2427C13.9285 12.0769 13.4587 11.994 12.7678 11.994C10.8609 11.994 9.75548 13.9838 9.39621 15.8078L9.2304 16.7197H13.2652L12.5467 20.4506H8.59477L5.3061 37.5296H0L3.26104 20.4506"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M21.4178 37.5296L21.5837 34.0475H21.4455C19.7873 36.8111 17.7975 37.8612 16.1394 37.8612C13.0994 37.8612 11.6071 35.2634 11.6071 31.6155C11.6071 25.3145 14.7576 16.3605 23.7116 16.3605C25.7843 16.3605 27.8846 16.7197 29.1559 17.2172L26.8621 28.7966C26.3647 31.2286 25.9778 35.2634 26.0054 37.5296H21.4178ZM23.3247 20.4506C22.9102 20.3677 22.5233 20.34 22.2193 20.34C18.7372 20.34 16.8579 27.2767 16.8026 30.3443C16.8026 32.1682 17.079 33.4947 18.3779 33.4947C19.8426 33.4947 21.1968 31.0904 21.9706 27.3043L23.3247 20.4506Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M29.7915 32.9697C30.6482 33.4395 31.6708 33.854 33.1631 33.8264C34.7107 33.7711 35.5398 32.8038 35.5398 31.4773C35.5398 30.3166 34.9871 29.5152 33.55 28.3821C31.7537 26.9174 30.897 25.0934 30.897 23.2971C30.897 19.4833 33.8816 16.3605 38.6074 16.3605C40.459 16.3605 41.7579 16.6921 42.5317 17.1343L41.371 21.0033C40.7906 20.6717 39.7957 20.3677 38.9114 20.3677C37.198 20.3677 36.1478 21.252 36.1478 22.6615C36.1478 23.7393 36.7558 24.3749 37.723 25.1763C40.0721 26.9727 40.8459 29.0177 40.8459 30.6206C40.8459 35.2358 37.6401 37.7783 32.8591 37.7783C30.9522 37.7783 29.2388 37.2256 28.5203 36.7282L29.7915 32.9697Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M53.3097 11.082L52.2319 16.7197H60.4121L59.7212 20.4506H51.5134L49.6894 30.1232C49.5512 30.8417 49.5236 31.4773 49.5236 31.8642C49.5236 33.1355 50.1592 33.6053 51.1265 33.6053C51.5134 33.6053 52.0661 33.6053 52.5635 33.5224L51.9279 37.5296C50.9606 37.7783 49.7723 37.8612 48.7498 37.8612C45.544 37.8612 43.9688 36.0925 43.9688 33.2736C43.9688 32.334 44.1346 31.1733 44.3557 30.1508L46.1796 20.4506H43.7753L44.4938 16.6921H46.9258L47.7549 12.4085L53.3097 11.082Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M54.581 37.5296L58.5882 16.7197H63.8943L59.9147 37.5296H54.581ZM59.5554 11.386C59.5554 9.83838 60.6609 8.09732 62.5401 8.09732C64.2812 8.09732 65.1103 9.42384 65.055 10.8609C64.9997 12.9889 63.5626 14.2049 61.9874 14.2049C60.274 14.2049 59.5278 12.9612 59.5554 11.386Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M67.2106 16.7197L67.3764 15.863C67.8462 13.3758 68.7858 11.2478 70.5269 9.64495C71.7981 8.4566 73.6774 7.73807 75.8054 7.73807C77.1595 7.73807 78.182 7.93152 78.8177 8.15261L77.6293 12.2427C77.1319 12.0769 76.6621 11.994 75.9712 11.994C74.0643 11.994 72.9589 13.9838 72.6272 15.8078L72.4338 16.7197H78.6242L77.9057 20.4506H71.7981L68.5371 37.5296H63.2034L66.492 20.4506"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M81.9405 16.7197L82.0787 26.7239C82.1063 28.6861 82.1616 30.1232 82.1063 31.6431H82.1892C82.6314 29.9574 83.046 28.5479 83.7645 26.337L86.915 16.6921H92.2764L85.4779 33.1078C83.4329 37.8612 81.0838 41.8684 78.5413 44.2175C77.2977 45.3506 75.8606 46.2625 74.9763 46.6218L72.8483 42.283C73.7603 41.8132 74.7552 41.2604 75.6119 40.5972C76.8002 39.6023 77.9333 38.4139 78.4584 37.2532C78.5413 36.9769 78.6242 36.7558 78.5689 36.3413L76.3581 16.6921H81.9405V16.7197Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5342_22458">
|
||||
<rect width="184" height="54.4151" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,47 @@
|
||||
export function NextjsLogo({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 184 38" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_5291_19580)">
|
||||
<path
|
||||
d="M122.318 0.0154419H154.367V5.93096H141.653V37.0517H135.296V5.93096H122.318V0.0154419Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M69.608 0.0154419V5.93096H43.9181V15.4472H64.5779V21.3627H43.9181V31.1362H69.608V37.0517H37.5612L37.5586 0.0154419H69.608Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M85.6114 0.0308914H77.2921L107.09 37.0672H115.433L100.533 18.5618L115.41 0.0591477L107.09 0.0720044L96.3674 13.3889L85.6114 0.0308914Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M94.1482 26.4861L89.9821 21.3061L77.2683 37.0954H85.6114L94.1482 26.4861Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M37.784 37.0517L7.94614 0H0V37.0363H6.35692V7.91648L29.7981 37.0517H37.784Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M155.796 36.8255C155.331 36.8255 154.935 36.6642 154.603 36.3417C154.271 36.0191 154.107 35.6286 154.112 35.166C154.107 34.7161 154.271 34.3298 154.603 34.0073C154.935 33.6847 155.331 33.5234 155.796 33.5234C156.244 33.5234 156.636 33.6847 156.967 34.0073C157.304 34.3298 157.471 34.7161 157.476 35.166C157.471 35.4716 157.394 35.7517 157.239 36.0021C157.079 36.2568 156.877 36.4563 156.623 36.6006C156.373 36.7491 156.097 36.8255 155.796 36.8255Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M166.646 21.2232H169.463V32.0761C169.459 33.0735 169.243 33.9266 168.821 34.6439C168.395 35.3612 167.805 35.9087 167.047 36.2949C166.293 36.6769 165.41 36.8722 164.406 36.8722C163.489 36.8722 162.666 36.7066 161.934 36.3841C161.202 36.0615 160.62 35.5777 160.194 34.941C159.763 34.3043 159.552 33.5107 159.552 32.5599H162.373C162.378 32.9759 162.472 33.3366 162.653 33.638C162.834 33.9393 163.084 34.1685 163.403 34.3298C163.725 34.4911 164.096 34.5717 164.514 34.5717C164.966 34.5717 165.354 34.4783 165.668 34.2873C165.982 34.1006 166.224 33.8205 166.392 33.447C166.555 33.0777 166.642 32.6193 166.646 32.0761V21.2232Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M181.054 25.4676C180.985 24.8097 180.683 24.2961 180.158 23.9311C179.628 23.5619 178.943 23.3793 178.103 23.3793C177.513 23.3793 177.005 23.4685 176.583 23.6425C176.161 23.8208 175.833 24.0584 175.609 24.3598C175.386 24.6611 175.274 25.0049 175.265 25.3912C175.265 25.7138 175.342 25.9939 175.493 26.2273C175.644 26.465 175.846 26.6645 176.109 26.8258C176.367 26.9913 176.656 27.1271 176.971 27.2375C177.289 27.3478 177.608 27.4412 177.927 27.5176L179.395 27.8784C179.986 28.0142 180.559 28.1967 181.105 28.4301C181.652 28.6594 182.148 28.9522 182.583 29.3045C183.018 29.6568 183.363 30.0812 183.617 30.5778C183.871 31.0744 184 31.6558 184 32.3265C184 33.2305 183.767 34.0242 183.298 34.7118C182.828 35.3951 182.152 35.9299 181.265 36.3162C180.382 36.6982 179.314 36.8934 178.056 36.8934C176.841 36.8934 175.782 36.7067 174.89 36.3332C173.994 35.9639 173.296 35.4206 172.792 34.7075C172.288 33.9945 172.017 33.1244 171.978 32.1015H174.77C174.808 32.6363 174.981 33.082 175.274 33.4427C175.571 33.7993 175.958 34.0624 176.432 34.2407C176.91 34.4147 177.444 34.5038 178.034 34.5038C178.65 34.5038 179.193 34.4104 179.662 34.2279C180.128 34.0454 180.494 33.7908 180.757 33.4597C181.024 33.1329 181.157 32.7467 181.162 32.3053C181.157 31.902 181.036 31.5667 180.804 31.3035C180.567 31.0404 180.24 30.8197 179.822 30.6415C179.4 30.4632 178.909 30.3019 178.349 30.1618L176.566 29.7119C175.278 29.3851 174.257 28.8886 173.512 28.2222C172.762 27.5558 172.392 26.673 172.392 25.5652C172.392 24.6569 172.641 23.859 173.146 23.1756C173.645 22.4923 174.33 21.9618 175.196 21.584C176.066 21.202 177.048 21.0152 178.142 21.0152C179.253 21.0152 180.227 21.202 181.067 21.584C181.906 21.9618 182.566 22.4881 183.044 23.1586C183.522 23.8293 183.772 24.5975 183.785 25.4676H181.054Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5291_19580">
|
||||
<rect width="184" height="37.3604" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
export function NuxtLogo({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 184 46" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_5336_22448)">
|
||||
<path
|
||||
d="M38.7097 46H64.3598C65.1746 46 65.9748 45.7875 66.6805 45.3838C67.3846 44.9814 67.9704 44.4006 68.379 43.7C68.7859 43.0017 69.0002 42.208 69 41.3998C68.9994 40.5916 68.7844 39.7981 68.3769 39.1002L51.1511 9.52867C50.7425 8.82816 50.1568 8.24752 49.4528 7.84507C48.7462 7.44109 47.9464 7.22869 47.1325 7.2289C46.3181 7.2289 45.5179 7.44165 44.8125 7.8453C44.1084 8.24768 43.5227 8.82834 43.1142 9.5289L38.7097 17.095L30.0978 2.29954C29.6891 1.59892 29.1031 1.01826 28.3988 0.61594C27.6921 0.212089 26.8922 -0.000228035 26.0783 1.8379e-07C25.2637 1.8379e-07 24.4633 0.21252 23.7576 0.61617C23.0534 1.01847 22.4674 1.59904 22.0586 2.29954L0.623071 39.1C0.215509 39.7979 0.000499188 40.5915 7.38785e-07 41.3998C-0.000459261 42.2073 0.213901 43.0006 0.621001 43.7C1.0296 44.4006 1.61541 44.9813 2.31955 45.3838C3.02626 45.7878 3.82623 46.0002 4.64025 46H20.7412C27.1207 46 31.8253 43.2228 35.0624 37.8044L42.9217 24.3144L47.1314 17.095L59.7653 38.7805H42.9217L38.7097 46ZM20.4787 38.7732L9.24232 38.7706L26.0857 9.85826L34.4901 24.3144L28.8629 33.9768C26.7131 37.4923 24.271 38.7732 20.4787 38.7732Z"
|
||||
fill="#00DC82"
|
||||
/>
|
||||
<path
|
||||
d="M86.71 46C86.954 46 87.188 45.9031 87.3605 45.7305C87.5331 45.558 87.63 45.324 87.63 45.08V23.69C87.63 23.69 88.8361 25.5958 91.08 29.44L99.9826 44.841C100.394 45.5584 101.152 46 101.972 46H108.1V11.5H101.89C101.646 11.5 101.412 11.5969 101.239 11.7695C101.067 11.942 100.97 12.176 100.97 12.42V34.04L97.06 27.14L88.6448 12.6546C88.2326 11.9398 87.4761 11.5 86.6578 11.5H80.5V46H86.71ZM155.527 33.0871L163.427 21.16H157.509C157.128 21.1607 156.754 21.2561 156.419 21.4377C156.084 21.6193 155.8 21.8813 155.592 22.2001L151.974 27.715L148.356 22.2001C148.148 21.8813 147.864 21.6193 147.529 21.4377C147.195 21.2561 146.82 21.1607 146.439 21.16H140.568L148.468 33.0379L139.961 46H145.703C146.08 45.9993 146.452 45.9054 146.785 45.7265C147.117 45.5477 147.401 45.2894 147.609 44.9747L152.021 38.3608L156.386 44.9664C156.595 45.2834 156.878 45.5438 157.212 45.7242C157.546 45.9046 157.919 45.9993 158.298 46H163.988L155.527 33.0871ZM166.658 21.16H171.192V13.8163H177.689V21.16H184V26.8771H177.689V36.685C177.689 39.1 178.905 40.2337 181.008 40.2337H184V46H180.12C174.698 46 171.192 42.7471 171.192 36.9808V26.8771H166.658V21.16ZM135.7 21.16H132.25C131.448 21.16 130.82 21.1934 130.295 21.7403C129.77 22.2569 129.72 22.5752 129.72 23.3655V35.4372C129.72 37.2906 129.634 38.4185 128.8 39.33C127.966 40.2111 126.818 40.48 125.12 40.48C123.453 40.48 122.274 40.2111 121.44 39.33C120.606 38.4185 120.52 37.2906 120.52 35.4372V23.3655C120.52 22.5754 120.47 22.2569 119.945 21.7403C119.42 21.1934 118.792 21.16 117.99 21.16H114.54V35.5171C114.54 38.738 115.435 41.2903 117.225 43.1742C119.047 45.0582 121.694 46 125.12 46C128.546 46 131.147 45.0582 132.968 43.1742C134.789 41.2903 135.7 38.738 135.7 35.5171V21.16Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5336_22448">
|
||||
<rect width="184" height="46" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,199 @@
|
||||
export function RemixLogo({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 204 66" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_d_5334_22407)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M47.4777 45.058C47.8835 50.3106 47.8835 52.7727 47.8835 55.4603H35.8237C35.8237 54.8748 35.8341 54.3393 35.8445 53.7962C35.8771 52.1081 35.9111 50.3479 35.6397 46.7928C35.2811 41.5885 33.0571 40.4319 28.9676 40.4319H25.3444H10V30.9627H29.5415C34.7071 30.9627 37.2898 29.379 37.2898 25.1865C37.2898 21.5001 34.7071 19.2659 29.5415 19.2659H10V10H31.6937C43.3882 10 49.1997 15.5659 49.1997 24.457C49.1997 31.1071 45.1102 35.4441 39.5857 36.167C44.2492 37.1068 46.9754 39.7813 47.4777 45.058Z"
|
||||
fill="#E5F3FF"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M10 55.4604V48.4012H22.7516C24.8815 48.4012 25.3439 49.9932 25.3439 50.9423V55.4604H10Z"
|
||||
fill="#E5F3FF"
|
||||
/>
|
||||
<g filter="url(#filter1_d_5334_22407)">
|
||||
<path
|
||||
d="M192.998 23.7133H181.045L175.606 31.3573L170.309 23.7133H157.498L169.021 39.5059L156.496 55.8753H168.448L174.819 47.1497L181.188 55.8753H194L181.403 39.0011L192.998 23.7133Z"
|
||||
fill="#FFF1F1"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter2_d_5334_22407)">
|
||||
<path
|
||||
d="M117.711 29.0434C116.351 25.2935 113.416 22.6975 107.762 22.6975C102.966 22.6975 99.5309 24.861 97.8132 28.3943V23.5629H86.2185V55.7246H97.8132V39.9322C97.8132 35.1008 99.1731 31.9279 102.966 31.9279C106.474 31.9279 107.333 34.2354 107.333 38.6343V55.7246H118.927V39.9322C118.927 35.1008 120.215 31.9279 124.08 31.9279C127.588 31.9279 128.375 34.2354 128.375 38.6343V55.7246H139.97V35.5335C139.97 28.827 137.393 22.6975 128.59 22.6975C123.222 22.6975 119.428 25.4377 117.711 29.0434Z"
|
||||
fill="#FFFAEA"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter3_d_5334_22407)">
|
||||
<path
|
||||
d="M74.1446 43.2432C73.071 45.7671 71.067 46.8487 67.9178 46.8487C64.4107 46.8487 61.5477 44.9738 61.2614 41.0076H83.6637V37.7628C83.6637 29.0372 78.0096 21.6817 67.345 21.6817C57.3964 21.6817 49.9529 28.9651 49.9529 39.1328C49.9529 49.3725 57.2534 55.5742 67.4883 55.5742C75.934 55.5742 81.803 51.464 83.4491 44.1084L74.1446 43.2432ZM61.4047 35.3829C61.8339 32.3542 63.4803 30.0466 67.202 30.0466C70.6375 30.0466 72.4983 32.4984 72.6415 35.3829H61.4047Z"
|
||||
fill="#F1FFF0"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter4_d_5334_22407)">
|
||||
<path
|
||||
d="M143.762 23.7765V55.9383H155.357V23.7765H143.762ZM143.691 20.7478H155.429V10.5078H143.691V20.7478Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_d_5334_22407"
|
||||
x="0"
|
||||
y="0"
|
||||
width="59.1997"
|
||||
height="65.4604"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0.203922 0 0 0 0 0.45098 0 0 0 0 0.74902 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5334_22407" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_5334_22407"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_d_5334_22407"
|
||||
x="146.496"
|
||||
y="13.7133"
|
||||
width="57.5044"
|
||||
height="52.162"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0.882353 0 0 0 0 0.180392 0 0 0 0 0.227451 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5334_22407" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_5334_22407"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter2_d_5334_22407"
|
||||
x="78.2185"
|
||||
y="14.6975"
|
||||
width="69.7512"
|
||||
height="49.0271"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="4" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0.937255 0 0 0 0 0.72549 0 0 0 0 0.117647 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5334_22407" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_5334_22407"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter3_d_5334_22407"
|
||||
x="41.9529"
|
||||
y="13.6817"
|
||||
width="49.7108"
|
||||
height="49.8926"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="4" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0.384314 0 0 0 0 0.788235 0 0 0 0 0.372549 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5334_22407" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_5334_22407"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
<filter
|
||||
id="filter4_d_5334_22407"
|
||||
x="133.691"
|
||||
y="0.507791"
|
||||
width="31.738"
|
||||
height="65.4305"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="5" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 0.596078 0 0 0 0 0.192157 0 0 0 0 0.556863 0 0 0 1 0"
|
||||
/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5334_22407" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="effect1_dropShadow_5334_22407"
|
||||
result="shape"
|
||||
/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export function SvelteKitLogo({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 184 34" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clipPath="url(#clip0_5341_22451)">
|
||||
<path
|
||||
d="M153.928 25.3304L149.002 16.1776L145.901 19.929V25.3304H143.3V7.82486H145.901V16.3027L152.653 7.82486H155.654L150.753 13.977L156.98 25.3304H153.928ZM162.606 25.3304V7.82486H165.206V25.3304H162.606ZM178.761 10.3257V25.3304H176.16V10.3257H171.208V7.82486H183.712V10.3257H178.761Z"
|
||||
fill="#8D8D93"
|
||||
/>
|
||||
<path
|
||||
d="M43.8554 25.6303C42.4293 25.6662 41.0258 25.2695 39.8294 24.4926C38.7117 23.7554 37.8816 22.6561 37.4785 21.3792L39.9292 20.4789C40.2691 21.2628 40.8241 21.9343 41.53 22.4157C42.2466 22.8959 43.0933 23.1447 43.9558 23.1285C44.7848 23.1719 45.603 22.9242 46.2688 22.4283C46.5585 22.1851 46.7873 21.8776 46.9371 21.5302C47.0869 21.1829 47.1535 20.8054 47.1316 20.4278C47.1349 20.076 47.0534 19.7285 46.8939 19.4148C46.7552 19.1342 46.5738 18.8768 46.3563 18.6518C46.0811 18.4012 45.7691 18.1946 45.431 18.0391C45.0139 17.8311 44.6679 17.6727 44.393 17.5639C44.1181 17.4552 43.7138 17.3093 43.1801 17.1264C42.5134 16.893 42.0133 16.7097 41.68 16.5764C41.2284 16.3842 40.7898 16.1628 40.367 15.9135C39.912 15.6721 39.4983 15.3598 39.1415 14.9882C38.8329 14.6257 38.5839 14.2162 38.4041 13.7754C38.0584 12.9348 37.9858 12.0069 38.1964 11.1229C38.4071 10.2388 38.8904 9.44335 39.5779 8.84903C40.5782 7.96552 41.9368 7.52366 43.6538 7.52347C45.0876 7.52347 46.2672 7.84021 47.1926 8.47369C48.0908 9.07097 48.7503 9.96521 49.0555 10.9998L46.655 11.7998C46.4138 11.258 46.0065 10.807 45.492 10.5121C44.8698 10.1667 44.1651 9.99797 43.4539 10.024C42.7565 9.98411 42.0657 10.1777 41.4907 10.5743C41.2559 10.7563 41.0685 10.9924 40.9445 11.2623C40.8204 11.5323 40.7634 11.8282 40.7782 12.1249C40.7802 12.3524 40.8303 12.5769 40.9251 12.7837C41.0198 12.9906 41.1572 13.1751 41.3281 13.3252C41.6604 13.6524 42.0504 13.9153 42.4785 14.1005C42.8791 14.2672 43.4878 14.4922 44.3044 14.7755C44.8041 14.9592 45.1749 15.0967 45.4169 15.1881C45.6589 15.2794 46.0132 15.4336 46.4798 15.6506C46.8539 15.816 47.2136 16.0123 47.555 16.2375C47.8608 16.4574 48.1532 16.6954 48.4304 16.9503C48.732 17.2117 48.9896 17.5199 49.1934 17.863C49.3877 18.2188 49.5387 18.5965 49.6433 18.9881C49.771 19.4482 49.8342 19.9239 49.8311 20.4014C49.8311 22.0354 49.2726 23.3149 48.1555 24.2399C47.0384 25.1649 45.605 25.6283 43.8554 25.6303ZM59.8851 25.3304L53.8832 7.82485H56.6843L60.6604 20.0287C60.8801 20.6851 61.0638 21.3531 61.2107 22.0295C61.357 21.3529 61.5407 20.685 61.7609 20.0287L65.6868 7.82485H68.4627L62.4858 25.3304H59.8851ZM73.764 25.3304V7.82485H84.6174V10.2758H76.365V15.0772H81.6916V17.5281H76.365V22.8794H85.1688V25.3304H73.764ZM91.7943 25.3304V7.82485H94.3955V22.8295H102.948V25.3304H91.7943ZM112.75 10.3257V25.3304H110.149V10.3257H105.198V7.82485H117.702V10.3257H112.75ZM123.078 25.3304V7.82485H133.931V10.2758H125.679V15.0772H131.006V17.5281H125.679V22.8794H134.483V25.3304H123.078Z"
|
||||
fill="#4A4A55"
|
||||
/>
|
||||
<path
|
||||
d="M25.615 4.49554C22.563 0.127376 16.5351 -1.16825 12.1769 1.60935L4.52282 6.48785C3.48875 7.13833 2.6018 7.99743 1.91867 9.01023C1.23553 10.023 0.771258 11.1672 0.555527 12.3697C0.190411 14.395 0.51127 16.4843 1.46731 18.3067C0.812002 19.3005 0.365144 20.417 0.153779 21.5885C-0.0643107 22.8151 -0.0352588 24.073 0.239231 25.2882C0.51372 26.5035 1.02812 27.6517 1.75226 28.6656C4.80425 33.0343 10.8325 34.3294 15.1901 31.5518L22.8445 26.6733C23.8786 26.0228 24.7655 25.1637 25.4487 24.1509C26.1318 23.1381 26.5961 21.9939 26.8118 20.7914C27.1767 18.766 26.856 16.6768 25.9003 14.8541C26.5553 13.8603 27.0019 12.744 27.2132 11.5726C27.4315 10.346 27.4025 9.08811 27.128 7.87282C26.8535 6.65752 26.3393 5.5093 25.615 4.49554Z"
|
||||
fill="#FF3E00"
|
||||
/>
|
||||
<path
|
||||
d="M11.4291 29.0767C10.2246 29.3898 8.95296 29.3257 7.78598 28.8932C6.619 28.4606 5.61287 27.6803 4.90345 26.6577C4.46805 26.0481 4.15874 25.3578 3.99365 24.6271C3.82857 23.8964 3.81103 23.1401 3.94207 22.4025C3.98642 22.1604 4.04751 21.9216 4.1249 21.6879L4.26898 21.2478L4.66134 21.5356C5.56664 22.2013 6.57896 22.7074 7.65464 23.0323L7.93901 23.1186L7.91289 23.4026C7.87805 23.8066 7.98742 24.2097 8.22161 24.5407C8.43522 24.849 8.73834 25.0842 9.08998 25.2146C9.44162 25.345 9.82483 25.3644 10.1878 25.2699C10.3539 25.2258 10.5122 25.1565 10.6573 25.0645L18.312 20.1845C18.4992 20.0666 18.6599 19.9109 18.7837 19.7275C18.9075 19.544 18.9917 19.3368 19.0309 19.119C19.0702 18.8966 19.0648 18.6687 19.015 18.4485C18.9652 18.2282 18.8721 18.0201 18.741 17.8363C18.5274 17.528 18.2243 17.2928 17.8727 17.1622C17.521 17.0317 17.1379 17.0122 16.7748 17.1064C16.6087 17.1505 16.4504 17.2198 16.3053 17.3119L13.3842 19.1739C12.9035 19.4793 12.379 19.7095 11.8288 19.8565C10.6243 20.1696 9.35266 20.1055 8.18568 19.6729C7.01869 19.2403 6.01256 18.4601 5.30314 17.4375C4.86762 16.8279 4.55821 16.1376 4.39307 15.4069C4.22793 14.6762 4.21039 13.9198 4.34147 13.1823C4.47158 12.4591 4.75119 11.7711 5.16242 11.1622C5.57365 10.5533 6.10744 10.0369 6.72965 9.64607L14.3834 4.76787C14.8639 4.46198 15.3885 4.23149 15.9388 4.0844C17.1433 3.77125 18.4149 3.8353 19.5819 4.26789C20.7489 4.70048 21.755 5.48077 22.4644 6.5034C22.8999 7.11296 23.2093 7.80332 23.3744 8.53401C23.5395 9.2647 23.5571 10.021 23.4261 10.7586C23.3812 11.0011 23.3197 11.2402 23.2421 11.4743L23.098 11.9145L22.706 11.6272C21.8007 10.9615 20.7884 10.4553 19.7127 10.1306L19.428 10.0443L19.4544 9.76023C19.4889 9.35635 19.3795 8.95328 19.1457 8.62219C18.9322 8.31419 18.6293 8.07913 18.278 7.94872C17.9266 7.81831 17.5437 7.79884 17.181 7.89294C17.0149 7.93707 16.8566 8.00635 16.7114 8.09836L9.05592 12.9766C8.86868 13.0944 8.70806 13.25 8.58432 13.4335C8.46058 13.6169 8.37643 13.8241 8.33723 14.0418C8.29778 14.2642 8.30305 14.4922 8.35274 14.7125C8.40242 14.9328 8.49551 15.1409 8.62659 15.3248C8.84026 15.6331 9.14336 15.8683 9.49496 15.9988C9.84656 16.1293 10.2297 16.1488 10.5928 16.0547C10.7588 16.0104 10.9171 15.9411 11.0623 15.8493L13.9828 13.9881C14.4633 13.6822 14.9878 13.4518 15.5382 13.3049C16.7424 12.9919 18.0136 13.0559 19.1803 13.4882C20.347 13.9205 21.3529 14.7004 22.0624 15.7225C22.4979 16.332 22.8073 17.0224 22.9725 17.7531C23.1376 18.4838 23.1552 19.2401 23.0241 19.9777C22.8943 20.7008 22.615 21.3889 22.2042 21.998C21.7934 22.607 21.26 23.1237 20.6382 23.515L12.9845 28.3932C12.504 28.6992 11.9795 28.9297 11.4291 29.0767Z"
|
||||
fill="white"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5341_22451">
|
||||
<rect width="184" height="33.1611" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -36,7 +36,8 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
|
||||
|
||||
const [form, { path, feedbackType, message }] = useForm({
|
||||
id: "accept-invite",
|
||||
lastSubmission,
|
||||
// TODO: type this
|
||||
lastSubmission: lastSubmission as any,
|
||||
onValidate({ formData }) {
|
||||
return parse(formData, { schema });
|
||||
},
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
import { Form } from "@remix-run/react";
|
||||
import { Paragraph } from "./primitives/Paragraph";
|
||||
import { Button } from "./primitives/Buttons";
|
||||
import { UserMinusIcon } from "@heroicons/react/20/solid";
|
||||
|
||||
export function ImpersonationBanner({ impersonationId }: { impersonationId: string }) {
|
||||
export function ImpersonationBanner() {
|
||||
return (
|
||||
<div className="grid h-8 grid-cols-3 items-center bg-gradient-to-r from-amber-500 via-amber-400 to-amber-500 px-4 text-center">
|
||||
<span></span>
|
||||
<Paragraph variant="small">
|
||||
You are impersonating <span className="font-semibold">{impersonationId}</span>
|
||||
</Paragraph>
|
||||
<Form
|
||||
action="/resources/impersonation"
|
||||
method="delete"
|
||||
reloadDocument
|
||||
className="justify-self-end"
|
||||
>
|
||||
<div className="w-full">
|
||||
<Form action="/resources/impersonation" method="delete" reloadDocument className="w-full">
|
||||
<Button
|
||||
type="submit"
|
||||
className="!text-slate-700 transition hover:!text-slate-900"
|
||||
variant="secondary/small"
|
||||
variant="small-menu-item"
|
||||
LeadingIcon={UserMinusIcon}
|
||||
fullWidth
|
||||
textAlignLeft
|
||||
>
|
||||
Stop impersonating
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
import {
|
||||
BoltIcon,
|
||||
CloudIcon,
|
||||
CodeBracketIcon,
|
||||
CodeBracketSquareIcon,
|
||||
HeartIcon,
|
||||
ServerStackIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import { useEffect, useState } from "react";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { LogoType } from "./LogoType";
|
||||
import { LinkButton } from "./primitives/Buttons";
|
||||
import { Header3 } from "./primitives/Headers";
|
||||
import { Icon } from "./primitives/Icon";
|
||||
import { Paragraph } from "./primitives/Paragraph";
|
||||
import { TextLink } from "./primitives/TextLink";
|
||||
import { LoginTooltip } from "./primitives/Tooltip";
|
||||
|
||||
interface QuoteType {
|
||||
quote: string;
|
||||
person: string;
|
||||
}
|
||||
|
||||
const quotes: QuoteType[] = [
|
||||
{
|
||||
quote: "Trigger.dev is redefining background jobs for modern developers.",
|
||||
person: "Paul Copplestone, Supabase",
|
||||
},
|
||||
{
|
||||
quote:
|
||||
"Trigger.dev is a great way to automate email campaigns with Resend, and we've heard nothing but good things from our mutual customers.",
|
||||
person: "Zeno Rocha, Resend",
|
||||
},
|
||||
{
|
||||
quote: "We love Trigger.dev and it’s had a big impact in dev iteration velocity already.",
|
||||
person: "André Neves, ZBD",
|
||||
},
|
||||
{
|
||||
quote:
|
||||
"We’ve been looking for a product like Trigger.dev for a really long time - automation that's simple and developer-focused.",
|
||||
person: "Han Wang, Mintlify",
|
||||
},
|
||||
];
|
||||
|
||||
const layout = "group grid place-items-center text-center overflow-hidden";
|
||||
const gridCell = "hover:bg-midnight-850 rounded-lg transition bg-midnight-850/40";
|
||||
const opacity = "opacity-10 group-hover:opacity-100 transition group-hover:scale-105";
|
||||
const logos = "h-[60%] w-[60%] transition grayscale group-hover:grayscale-0";
|
||||
const features = "h-[60%] w-[60%] text-gray-500 grayscale transition group-hover:grayscale-0";
|
||||
const wide = "col-span-2";
|
||||
const wider = "col-span-3 row-span-2";
|
||||
const mediumSquare = "col-span-2 row-span-2";
|
||||
const hidden = "hidden xl:grid";
|
||||
|
||||
export function LoginPageLayout({ children }: { children: React.ReactNode }) {
|
||||
const [randomQuote, setRandomQuote] = useState<QuoteType | null>(null);
|
||||
useEffect(() => {
|
||||
const randomIndex = Math.floor(Math.random() * quotes.length);
|
||||
setRandomQuote(quotes[randomIndex]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<main className="grid h-full grid-cols-12">
|
||||
<div className="border-midnight-750 z-10 col-span-12 border-r bg-midnight-850 md:col-span-6">
|
||||
<div className="flex h-full flex-col items-center justify-between p-6">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<a href="https://trigger.dev">
|
||||
<LogoType className="w-36" />
|
||||
</a>
|
||||
<LinkButton
|
||||
to="https://trigger.dev/docs"
|
||||
variant={"secondary/small"}
|
||||
LeadingIcon="docs"
|
||||
>
|
||||
Documentation
|
||||
</LinkButton>
|
||||
</div>
|
||||
<div className="flex h-full max-w-sm items-center justify-center">{children}</div>
|
||||
<Paragraph variant="extra-small" className="text-center">
|
||||
Having login issues? <TextLink href="mailto:help@trigger.dev">Email us</TextLink> or{" "}
|
||||
<TextLink href="https://trigger.dev/discord">ask us in Discord</TextLink>
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
<div className="hidden grid-cols-3 grid-rows-6 gap-4 p-4 md:col-span-6 md:grid xl:grid-cols-5">
|
||||
<LoginTooltip side="bottom" content={<ServerlessTooltipContent />}>
|
||||
<div className={cn(layout, gridCell, mediumSquare)}>
|
||||
<ServerStackIcon className={cn(opacity, features, "group-hover:text-green-500")} />
|
||||
</div>
|
||||
</LoginTooltip>
|
||||
<LoginTooltip side="bottom" content={<SlackTooltipContent />}>
|
||||
<div className={cn(layout, gridCell)}>
|
||||
<Icon icon="slack" className={cn(logos, opacity)} />
|
||||
</div>
|
||||
</LoginTooltip>
|
||||
<LoginTooltip side="left" content={<TriggerTooltipContent />}>
|
||||
<div className={cn(layout, gridCell, mediumSquare, hidden)}>
|
||||
<BoltIcon className={cn(opacity, features, "group-hover:text-yellow-500")} />
|
||||
</div>
|
||||
</LoginTooltip>
|
||||
<LoginTooltip side="bottom" content={<StripeTooltipContent />} className="max-w-[15rem]">
|
||||
<div className={cn("", layout, gridCell)}>
|
||||
<Icon icon="stripe" className={cn(logos, opacity)} />
|
||||
</div>
|
||||
</LoginTooltip>
|
||||
<LoginTooltip side="top" content={<QuoteTooltipContent />}>
|
||||
<div className={cn(layout, gridCell, wider)}>
|
||||
<div className="px-4">
|
||||
<Header3 className="relative text-2xl font-normal leading-8 text-gray-600 transition before:relative before:right-1 before:top-0 before:text-4xl before:text-slate-600 before:opacity-20 before:content-['❝'] group-hover:text-slate-500 group-hover:before:opacity-30 lg-height:text-xl md-height:text-lg">
|
||||
{randomQuote?.quote}
|
||||
</Header3>
|
||||
<Paragraph
|
||||
variant="small"
|
||||
className="mt-4 text-gray-700 transition group-hover:text-slate-600"
|
||||
>
|
||||
{randomQuote?.person}
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</LoginTooltip>
|
||||
<LoginTooltip side="left" content={<OpenaiTooltipContent />}>
|
||||
<div className={cn("", layout, gridCell, hidden)}>
|
||||
<Icon icon="openai" className={cn(logos, opacity)} />
|
||||
</div>
|
||||
</LoginTooltip>
|
||||
<LoginTooltip side="left" content={<SendgridTooltipContent />}>
|
||||
<div className={cn(layout, gridCell, hidden)}>
|
||||
<Icon icon="sendgrid" className={cn(logos, opacity)} />
|
||||
</div>
|
||||
</LoginTooltip>
|
||||
<LoginTooltip side="left" content={<ReactHooksTooltipContent />}>
|
||||
<div className={cn(layout, gridCell, mediumSquare, hidden)}>
|
||||
<Icon icon="react" className={cn(opacity, features, "group-hover:text-green-500")} />
|
||||
</div>
|
||||
</LoginTooltip>
|
||||
<LoginTooltip side="right" content={<AirtableTooltipContent />}>
|
||||
<div className={cn("", layout, gridCell)}>
|
||||
<Icon icon="airtable" className={cn(logos, opacity)} />
|
||||
</div>
|
||||
</LoginTooltip>
|
||||
<LoginTooltip side="top" content={<InYourCodebaseTooltipContent />}>
|
||||
<div className={cn(layout, gridCell, mediumSquare)}>
|
||||
<CodeBracketSquareIcon className={cn(opacity, features, "group-hover:text-rose-500")} />
|
||||
</div>
|
||||
</LoginTooltip>
|
||||
<LoginTooltip side="right" content={<SupabaseTooltipContent />}>
|
||||
<div className={cn(layout, gridCell)}>
|
||||
<Icon icon="supabase" className={cn(logos, opacity)} />
|
||||
</div>
|
||||
</LoginTooltip>
|
||||
<LoginTooltip side="left" content={<CloudTooltipContent />}>
|
||||
<div className={cn(layout, gridCell, wide, hidden)}>
|
||||
<CloudIcon className={cn(opacity, features, "h-20 w-20 group-hover:text-blue-600")} />
|
||||
</div>
|
||||
</LoginTooltip>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function SlackTooltipContent() {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 flex items-center gap-x-1.5">
|
||||
<Icon icon="slack" className="h-5 w-5" />
|
||||
<Paragraph variant="base/bright" className="font-semibold">
|
||||
Slack Integration
|
||||
</Paragraph>
|
||||
</div>
|
||||
<Paragraph variant="base">Post messages to your team when your Job is triggered.</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function StripeTooltipContent() {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 flex items-center gap-x-1.5">
|
||||
<Icon icon="stripe" className="h-5 w-5" />
|
||||
<Paragraph variant="base/bright" className="font-semibold">
|
||||
Stripe Integration
|
||||
</Paragraph>
|
||||
</div>
|
||||
<Paragraph variant="base">Trigger payments, emails, subscription upgrades…</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SupabaseTooltipContent() {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 flex items-center gap-x-1.5">
|
||||
<Icon icon="supabase" className="h-5 w-5" />
|
||||
<Paragraph variant="base/bright" className="font-semibold">
|
||||
Supabase Integration
|
||||
</Paragraph>
|
||||
</div>
|
||||
<Paragraph variant="base">React to changes in your database.</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SendgridTooltipContent() {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 flex items-center gap-x-1.5">
|
||||
<Icon icon="sendgrid" className="h-5 w-5" />
|
||||
<Paragraph variant="base/bright" className="font-semibold">
|
||||
SendGrid Integration
|
||||
</Paragraph>
|
||||
</div>
|
||||
<Paragraph variant="base">
|
||||
Create a drip campaign, trigger an onboarding sequence and more…
|
||||
</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function AirtableTooltipContent() {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 flex items-center gap-x-1.5">
|
||||
<Icon icon="airtable" className="h-5 w-5" />
|
||||
<Paragraph variant="base/bright" className="font-semibold">
|
||||
Airtable Integration
|
||||
</Paragraph>
|
||||
</div>
|
||||
<Paragraph variant="base">
|
||||
Update your Airtable records when you make a Stripe sale, receive a new Typeform response
|
||||
and more…
|
||||
</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function OpenaiTooltipContent() {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 flex items-center gap-x-1.5">
|
||||
<Icon icon="openai" className="h-5 w-5" />
|
||||
<Paragraph variant="base/bright" className="font-semibold">
|
||||
OpenAI Integration
|
||||
</Paragraph>
|
||||
</div>
|
||||
<Paragraph variant="base">Generate text, images, code and more with OpenAI's API.</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function TriggerTooltipContent() {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 flex items-center gap-x-1.5">
|
||||
<BoltIcon className="h-5 w-5 text-yellow-500" />
|
||||
<Paragraph variant="base/bright" className="font-semibold">
|
||||
Triggering your Job
|
||||
</Paragraph>
|
||||
</div>
|
||||
<Paragraph variant="base">
|
||||
Trigger your Jobs with a webhook, on a recurring schedule, or from your own custom events.
|
||||
</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function QuoteTooltipContent() {
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center gap-x-1.5">
|
||||
<HeartIcon className="h-5 w-5 text-rose-500" />
|
||||
<Paragraph variant="base/bright" className="font-semibold">
|
||||
Loved by developers
|
||||
</Paragraph>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function InYourCodebaseTooltipContent() {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 flex items-center gap-x-1.5">
|
||||
<CodeBracketSquareIcon className="h-5 w-5 text-rose-500" />
|
||||
<Paragraph variant="base/bright" className="font-semibold">
|
||||
In your codebase
|
||||
</Paragraph>
|
||||
</div>
|
||||
<Paragraph variant="base">
|
||||
Create background jobs where they belong: in your codebase.
|
||||
</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function CloudTooltipContent() {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 flex items-center gap-x-1.5">
|
||||
<CloudIcon className="h-5 w-5 text-blue-600" />
|
||||
<Paragraph variant="base/bright" className="font-semibold">
|
||||
Zero infrastructure
|
||||
</Paragraph>
|
||||
</div>
|
||||
<Paragraph variant="base">
|
||||
Use our SDK to write Jobs in your codebase and deploy as you normally do.
|
||||
</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ServerlessTooltipContent() {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 flex items-center gap-x-1.5">
|
||||
<ServerStackIcon className="h-5 w-5 text-green-500" />
|
||||
<Paragraph variant="base/bright" className="font-semibold">
|
||||
Full serverless support
|
||||
</Paragraph>
|
||||
</div>
|
||||
<Paragraph variant="base">
|
||||
Run long-running background jobs without worrying about timeouts.
|
||||
</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ReactHooksTooltipContent() {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-2 flex items-center gap-x-1.5">
|
||||
<Icon icon="react" className="h-5 w-5" />
|
||||
<Paragraph variant="base/bright" className="font-semibold">
|
||||
Show Job progress in your UI
|
||||
</Paragraph>
|
||||
</div>
|
||||
<Paragraph variant="base">
|
||||
Use our React hooks to display a real-time status to your users.
|
||||
</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
export function LogoType({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg viewBox="0 0 751 130" xmlns="http://www.w3.org/2000/svg" className={className}>
|
||||
<path
|
||||
d="M195.022 16.2676H135.445H137.799V32.5096H157.858V102.4H174.84V32.5096H195.022V16.2676Z"
|
||||
fill="url(#paint0_linear_228_1439)"
|
||||
/>
|
||||
<path
|
||||
d="M211.265 51.4587V40.8767H195.391V102.4H211.265V72.9917C211.265 60.0719 221.725 56.3805 229.97 57.3648V39.6463C222.218 39.6463 214.465 43.0916 211.265 51.4587Z"
|
||||
fill="url(#paint1_linear_228_1439)"
|
||||
/>
|
||||
<path
|
||||
d="M246.954 33.494C252.368 33.494 256.799 29.0644 256.799 23.7734C256.799 18.4824 252.368 13.9297 246.954 13.9297C241.662 13.9297 237.232 18.4824 237.232 23.7734C237.232 29.0644 241.662 33.494 246.954 33.494ZM239.078 102.4H254.953V40.8767H239.078V102.4Z"
|
||||
fill="url(#paint2_linear_228_1439)"
|
||||
/>
|
||||
<path
|
||||
d="M315.253 40.8768V48.5056C310.946 42.7224 304.301 39.1542 295.563 39.1542C278.089 39.1542 264.921 53.4275 264.921 70.6539C264.921 88.0033 278.089 102.154 295.563 102.154C304.301 102.154 310.946 98.5853 315.253 92.8021V99.4466C315.253 109.167 309.1 114.581 299.132 114.581C289.656 114.581 285.596 110.767 283.011 105.968L269.475 113.72C274.889 123.687 285.472 128.731 298.64 128.731C314.884 128.731 330.758 119.626 330.758 99.4466V40.8768H315.253ZM298.025 87.5112C288.057 87.5112 280.796 80.4975 280.796 70.6539C280.796 60.9332 288.057 53.9196 298.025 53.9196C307.992 53.9196 315.253 60.9332 315.253 70.6539C315.253 80.4975 307.992 87.5112 298.025 87.5112Z"
|
||||
fill="url(#paint3_linear_228_1439)"
|
||||
/>
|
||||
<path
|
||||
d="M390.936 40.8768V48.5056C386.629 42.7224 379.983 39.1542 371.246 39.1542C353.772 39.1542 340.604 53.4275 340.604 70.6539C340.604 88.0033 353.772 102.154 371.246 102.154C379.983 102.154 386.629 98.5853 390.936 92.8021V99.4466C390.936 109.167 384.783 114.581 374.815 114.581C365.339 114.581 361.278 110.767 358.694 105.968L345.157 113.72C350.572 123.687 361.155 128.731 374.322 128.731C390.566 128.731 406.441 119.626 406.441 99.4466V40.8768H390.936ZM373.707 87.5112C363.739 87.5112 356.479 80.4975 356.479 70.6539C356.479 60.9332 363.739 53.9196 373.707 53.9196C383.675 53.9196 390.936 60.9332 390.936 70.6539C390.936 80.4975 383.675 87.5112 373.707 87.5112Z"
|
||||
fill="url(#paint4_linear_228_1439)"
|
||||
/>
|
||||
<path
|
||||
d="M432.9 78.1597H479.293C479.663 76.0679 479.909 73.9761 479.909 71.6383C479.909 53.5505 466.987 39.1542 448.775 39.1542C429.454 39.1542 416.287 53.3044 416.287 71.6383C416.287 89.9721 429.331 104.122 450.005 104.122C461.819 104.122 471.048 99.3236 476.832 90.9564L464.034 83.5737C461.327 87.142 456.404 89.726 450.251 89.726C441.883 89.726 435.115 86.2807 432.9 78.1597ZM432.654 65.8551C434.5 57.9802 440.284 53.4274 448.775 53.4274C455.42 53.4274 462.065 56.9958 464.034 65.8551H432.654Z"
|
||||
fill="url(#paint5_linear_228_1439)"
|
||||
/>
|
||||
<path
|
||||
d="M505.199 51.4587V40.8767H489.324V102.4H505.199V72.9917C505.199 60.0719 515.659 56.3805 523.904 57.3648V39.6463C516.151 39.6463 508.398 43.0916 505.199 51.4587Z"
|
||||
fill="url(#paint6_linear_228_1439)"
|
||||
/>
|
||||
<path
|
||||
d="M529.934 103.999C535.717 103.999 540.394 99.3235 540.394 93.5404C540.394 87.7572 535.717 83.0815 529.934 83.0815C524.15 83.0815 519.473 87.7572 519.473 93.5404C519.473 99.3235 524.15 103.999 529.934 103.999Z"
|
||||
fill="url(#paint7_linear_228_1439)"
|
||||
/>
|
||||
<path
|
||||
d="M596.632 16.2676V48.1364C592.202 42.4763 585.679 39.1541 576.696 39.1541C560.206 39.1541 546.67 53.3044 546.67 71.6382C546.67 89.972 560.206 104.122 576.696 104.122C585.679 104.122 592.202 100.8 596.632 95.1399V102.4H612.506V16.2676L596.632 16.2676ZM579.65 88.9876C569.805 88.9876 562.544 81.9741 562.544 71.6382C562.544 61.3024 569.805 54.2887 579.65 54.2887C589.371 54.2887 596.632 61.3024 596.632 71.6382C596.632 81.9741 589.371 88.9876 579.65 88.9876Z"
|
||||
fill="url(#paint8_linear_228_1439)"
|
||||
/>
|
||||
<path
|
||||
d="M637.98 78.1597H684.373C684.742 76.0679 684.989 73.9761 684.989 71.6383C684.989 53.5505 672.067 39.1542 653.855 39.1542C634.534 39.1542 621.367 53.3044 621.367 71.6383C621.367 89.9721 634.411 104.122 655.085 104.122C666.899 104.122 676.128 99.3236 681.912 90.9564L669.114 83.5737C666.407 87.142 661.484 89.726 655.331 89.726C646.963 89.726 640.195 86.2807 637.98 78.1597ZM637.734 65.8551C639.58 57.9802 645.363 53.4274 653.855 53.4274C660.5 53.4274 667.145 56.9958 669.114 65.8551H637.734Z"
|
||||
fill="url(#paint9_linear_228_1439)"
|
||||
/>
|
||||
<path
|
||||
d="M732.859 40.8768L717.846 83.9428L702.955 40.8768H685.481L708.862 102.4H726.952L750.333 40.8768H732.859Z"
|
||||
fill="url(#paint10_linear_228_1439)"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M35.664 42.3949L59.4114 1.26865L118.264 103.194H0.558823L24.3062 62.0665L41.1046 71.7643L34.157 83.7971H84.6657L59.4114 40.0612L52.4637 52.094L35.664 42.3949Z"
|
||||
fill="url(#paint11_linear_228_1439)"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="paint0_linear_228_1439"
|
||||
x1="329.674"
|
||||
y1="150.079"
|
||||
x2="329.674"
|
||||
y2="13.9297"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#41FF54" />
|
||||
<stop offset="1" stopColor="#E7FF52" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_228_1439"
|
||||
x1="329.674"
|
||||
y1="150.079"
|
||||
x2="329.674"
|
||||
y2="13.9297"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#41FF54" />
|
||||
<stop offset="1" stopColor="#E7FF52" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_228_1439"
|
||||
x1="329.674"
|
||||
y1="150.079"
|
||||
x2="329.674"
|
||||
y2="13.9297"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#41FF54" />
|
||||
<stop offset="1" stopColor="#E7FF52" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint3_linear_228_1439"
|
||||
x1="329.674"
|
||||
y1="150.079"
|
||||
x2="329.674"
|
||||
y2="13.9297"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#41FF54" />
|
||||
<stop offset="1" stopColor="#E7FF52" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint4_linear_228_1439"
|
||||
x1="329.674"
|
||||
y1="150.079"
|
||||
x2="329.674"
|
||||
y2="13.9297"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#41FF54" />
|
||||
<stop offset="1" stopColor="#E7FF52" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint5_linear_228_1439"
|
||||
x1="329.674"
|
||||
y1="150.079"
|
||||
x2="329.674"
|
||||
y2="13.9297"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#41FF54" />
|
||||
<stop offset="1" stopColor="#E7FF52" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint6_linear_228_1439"
|
||||
x1="329.674"
|
||||
y1="150.079"
|
||||
x2="329.674"
|
||||
y2="13.9297"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#41FF54" />
|
||||
<stop offset="1" stopColor="#E7FF52" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint7_linear_228_1439"
|
||||
x1="634.903"
|
||||
y1="139.717"
|
||||
x2="651.436"
|
||||
y2="25.9719"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#2563EB" />
|
||||
<stop offset="1" stopColor="#A855F7" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint8_linear_228_1439"
|
||||
x1="634.903"
|
||||
y1="139.717"
|
||||
x2="651.436"
|
||||
y2="25.9719"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#2563EB" />
|
||||
<stop offset="1" stopColor="#A855F7" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint9_linear_228_1439"
|
||||
x1="634.903"
|
||||
y1="139.717"
|
||||
x2="651.436"
|
||||
y2="25.9719"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#2563EB" />
|
||||
<stop offset="1" stopColor="#A855F7" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint10_linear_228_1439"
|
||||
x1="634.903"
|
||||
y1="139.717"
|
||||
x2="651.436"
|
||||
y2="25.9719"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#2563EB" />
|
||||
<stop offset="1" stopColor="#A855F7" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint11_linear_228_1439"
|
||||
x1="95.8593"
|
||||
y1="103.194"
|
||||
x2="94.7607"
|
||||
y2="31.2381"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#41FF54" />
|
||||
<stop offset="1" stopColor="#E7FF52" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import gradientBackground from "~/assets/images/gradient-background.png";
|
||||
|
||||
export function PageGradient({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div
|
||||
className="-mt-4 h-full w-full bg-cover bg-no-repeat pt-20"
|
||||
style={{ backgroundImage: `url("${gradientBackground}")` }}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { InlineCode } from "./code/InlineCode";
|
||||
import {
|
||||
ClientTabs,
|
||||
ClientTabsContent,
|
||||
ClientTabsList,
|
||||
ClientTabsTrigger,
|
||||
} from "./primitives/ClientTabs";
|
||||
import { ClipboardField } from "./primitives/ClipboardField";
|
||||
import { Paragraph } from "./primitives/Paragraph";
|
||||
|
||||
export function InitCommand({ appOrigin, apiKey }: { appOrigin: string; apiKey: string }) {
|
||||
return (
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
secure={`npx @trigger.dev/cli@latest init -k ••••••••• -t ${appOrigin}`}
|
||||
value={`npx @trigger.dev/cli@latest init -k ${apiKey} -t ${appOrigin}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
secure={`pnpm dlx @trigger.dev/cli@latest init -k ••••••••• -t ${appOrigin}`}
|
||||
value={`pnpm dlx @trigger.dev/cli@latest init -k ${apiKey} -t ${appOrigin}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
secure={`yarn dlx @trigger.dev/cli@latest init -k ••••••••• -t ${appOrigin}`}
|
||||
value={`yarn dlx @trigger.dev/cli@latest init -k ${apiKey} -t ${appOrigin}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export function RunDevCommand({ extra }: { extra?: string }) {
|
||||
return (
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`npm run dev${extra ? ` ${extra}` : ""}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`pnpm run dev${extra ? ` ${extra}` : ""}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`yarn run dev${extra ? ` ${extra}` : ""}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export function TriggerDevCommand({ extra }: { extra?: string }) {
|
||||
return (
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`npx @trigger.dev/cli@latest dev${extra ? ` ${extra}` : ""}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`pnpm dlx @trigger.dev/cli@latest dev${extra ? ` ${extra}` : ""}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`yarn dlx @trigger.dev/cli@latest dev${extra ? ` ${extra}` : ""}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export function TriggerDevStep({ extra }: { extra?: string }) {
|
||||
return (
|
||||
<>
|
||||
<Paragraph spacing>
|
||||
In a <span className="text-amber-400">separate terminal window or tab</span> run:
|
||||
</Paragraph>
|
||||
<TriggerDevCommand extra={extra} />
|
||||
<Paragraph spacing variant="small">
|
||||
If you’re not running on the default you can specify the port by adding{" "}
|
||||
<InlineCode variant="extra-small">--port 3001</InlineCode> to the end.
|
||||
</Paragraph>
|
||||
<Paragraph spacing variant="small">
|
||||
You should leave the <InlineCode variant="extra-small">dev</InlineCode> command running when
|
||||
you're developing.
|
||||
</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { cn } from "~/utils/cn";
|
||||
|
||||
export function StepContentContainer({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return <div className={cn("mb-6 ml-9 mt-1", className)}>{children}</div>;
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
import { Clipboard, ClipboardCheck } from "lucide-react";
|
||||
import type { Language, PrismTheme } from "prism-react-renderer";
|
||||
import Highlight, { defaultProps } from "prism-react-renderer";
|
||||
import { forwardRef, useCallback, useState } from "react";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip";
|
||||
import { ClipboardDocumentCheckIcon, ClipboardIcon } from "@heroicons/react/24/solid";
|
||||
import { Clipboard, ClipboardCheck, ClipboardCheckIcon } from "lucide-react";
|
||||
|
||||
//This is a fork of https://github.com/mantinedev/mantine/blob/master/src/mantine-prism/src/Prism/Prism.tsx
|
||||
//it didn't support highlighting lines by dimming the rest of the code, or animations on the highlighting
|
||||
@@ -192,6 +191,9 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||
Array.from({ length: end - start + 1 }, (_, i) => start + i)
|
||||
);
|
||||
|
||||
// if there are more than 1000 lines, don't highlight
|
||||
const shouldHighlight = lineCount <= 1000;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("relative overflow-hidden rounded-md border border-slate-800", className)}
|
||||
@@ -229,99 +231,113 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||
</TooltipProvider>
|
||||
)}
|
||||
|
||||
<Highlight {...defaultProps} theme={theme} code={code} language={language}>
|
||||
{({
|
||||
className: inheritedClassName,
|
||||
style: inheritedStyle,
|
||||
tokens,
|
||||
getLineProps,
|
||||
getTokenProps,
|
||||
}) => (
|
||||
<div
|
||||
dir="ltr"
|
||||
className="overflow-auto px-2 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700"
|
||||
style={{
|
||||
maxHeight,
|
||||
}}
|
||||
>
|
||||
<pre
|
||||
className={cn(
|
||||
"relative mr-2 font-mono text-xs leading-relaxed",
|
||||
inheritedClassName
|
||||
)}
|
||||
style={inheritedStyle}
|
||||
{shouldHighlight ? (
|
||||
<Highlight {...defaultProps} theme={theme} code={code} language={language}>
|
||||
{({
|
||||
className: inheritedClassName,
|
||||
style: inheritedStyle,
|
||||
tokens,
|
||||
getLineProps,
|
||||
getTokenProps,
|
||||
}) => (
|
||||
<div
|
||||
dir="ltr"
|
||||
className="overflow-auto px-2 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700"
|
||||
style={{
|
||||
maxHeight,
|
||||
}}
|
||||
>
|
||||
{tokens
|
||||
.map((line, index) => {
|
||||
if (
|
||||
index === tokens.length - 1 &&
|
||||
line.length === 1 &&
|
||||
line[0].content === "\n"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
<pre
|
||||
className={cn(
|
||||
"relative mr-2 font-mono text-xs leading-relaxed",
|
||||
inheritedClassName
|
||||
)}
|
||||
style={inheritedStyle}
|
||||
dir="ltr"
|
||||
>
|
||||
{tokens
|
||||
.map((line, index) => {
|
||||
if (
|
||||
index === tokens.length - 1 &&
|
||||
line.length === 1 &&
|
||||
line[0].content === "\n"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lineNumber = index + 1;
|
||||
const lineProps = getLineProps({ line, key: index });
|
||||
const lineNumber = index + 1;
|
||||
const lineProps = getLineProps({ line, key: index });
|
||||
|
||||
let hasAnyHighlights = highlightLines ? highlightLines.length > 0 : false;
|
||||
let hasAnyHighlights = highlightLines ? highlightLines.length > 0 : false;
|
||||
|
||||
let shouldDim = hasAnyHighlights;
|
||||
if (hasAnyHighlights && highlightLines?.includes(lineNumber)) {
|
||||
shouldDim = false;
|
||||
}
|
||||
let shouldDim = hasAnyHighlights;
|
||||
if (hasAnyHighlights && highlightLines?.includes(lineNumber)) {
|
||||
shouldDim = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={lineProps.key}
|
||||
{...lineProps}
|
||||
className={cn(
|
||||
"flex w-full justify-start transition-opacity duration-500",
|
||||
lineProps.className
|
||||
)}
|
||||
style={{
|
||||
opacity: shouldDim ? dimAmount : undefined,
|
||||
...lineProps.style,
|
||||
}}
|
||||
>
|
||||
{showLineNumbers && (
|
||||
<div
|
||||
className={
|
||||
"mr-2 flex-none select-none text-right text-slate-500 transition-opacity duration-500"
|
||||
}
|
||||
style={{
|
||||
width: `calc(8 * ${maxLineWidth / 16}rem)`,
|
||||
}}
|
||||
>
|
||||
{lineNumber}
|
||||
return (
|
||||
<div
|
||||
key={lineProps.key}
|
||||
{...lineProps}
|
||||
className={cn(
|
||||
"flex w-full justify-start transition-opacity duration-500",
|
||||
lineProps.className
|
||||
)}
|
||||
style={{
|
||||
opacity: shouldDim ? dimAmount : undefined,
|
||||
...lineProps.style,
|
||||
}}
|
||||
>
|
||||
{showLineNumbers && (
|
||||
<div
|
||||
className={
|
||||
"mr-2 flex-none select-none text-right text-slate-500 transition-opacity duration-500"
|
||||
}
|
||||
style={{
|
||||
width: `calc(8 * ${maxLineWidth / 16}rem)`,
|
||||
}}
|
||||
>
|
||||
{lineNumber}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1">
|
||||
{line.map((token, key) => {
|
||||
const tokenProps = getTokenProps({ token, key });
|
||||
return (
|
||||
<span
|
||||
key={tokenProps.key}
|
||||
{...tokenProps}
|
||||
style={{
|
||||
color: tokenProps?.style?.color as string,
|
||||
...tokenProps.style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1">
|
||||
{line.map((token, key) => {
|
||||
const tokenProps = getTokenProps({ token, key });
|
||||
return (
|
||||
<span
|
||||
key={tokenProps.key}
|
||||
{...tokenProps}
|
||||
style={{
|
||||
color: tokenProps?.style?.color as string,
|
||||
...tokenProps.style,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<div className="w-4 flex-none" />
|
||||
</div>
|
||||
<div className="w-4 flex-none" />
|
||||
</div>
|
||||
);
|
||||
})
|
||||
.filter(Boolean)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</Highlight>
|
||||
);
|
||||
})
|
||||
.filter(Boolean)}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</Highlight>
|
||||
) : (
|
||||
<div
|
||||
dir="ltr"
|
||||
className="overflow-auto px-2 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700"
|
||||
style={{
|
||||
maxHeight,
|
||||
}}
|
||||
>
|
||||
<pre className="relative mr-2 p-2 font-mono text-xs leading-relaxed" dir="ltr">
|
||||
{code}
|
||||
</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
ClientTabs,
|
||||
ClientTabsList,
|
||||
ClientTabsTrigger,
|
||||
ClientTabsContent,
|
||||
} from "../primitives/ClientTabs";
|
||||
import { ClipboardField } from "../primitives/ClipboardField";
|
||||
|
||||
type InstallPackagesProps = {
|
||||
packages: string[];
|
||||
};
|
||||
|
||||
export function InstallPackages({ packages }: InstallPackagesProps) {
|
||||
return (
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`npm install ${packages.join(" ")}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`pnpm install ${packages.join(" ")}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`yarn add ${packages.join(" ")}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
);
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
import { json as jsonLang } from "@codemirror/lang-json";
|
||||
import type { ViewUpdate } from "@codemirror/view";
|
||||
import { CheckIcon, ClipboardIcon } from "@heroicons/react/20/solid";
|
||||
import type { ReactCodeMirrorProps, UseCodeMirror } from "@uiw/react-codemirror";
|
||||
import { useCodeMirror } from "@uiw/react-codemirror";
|
||||
import { useRef, useEffect } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Button } from "../primitives/Buttons";
|
||||
import { getEditorSetup } from "./codeMirrorSetup";
|
||||
import { darkTheme } from "./codeMirrorTheme";
|
||||
import { cn } from "~/utils/cn";
|
||||
|
||||
export interface JSONEditorProps extends Omit<ReactCodeMirrorProps, "onBlur"> {
|
||||
defaultValue?: string;
|
||||
@@ -14,6 +16,8 @@ export interface JSONEditorProps extends Omit<ReactCodeMirrorProps, "onBlur"> {
|
||||
onChange?: (value: string) => void;
|
||||
onUpdate?: (update: ViewUpdate) => void;
|
||||
onBlur?: (code: string) => void;
|
||||
showCopyButton?: boolean;
|
||||
showClearButton?: boolean;
|
||||
}
|
||||
|
||||
const languages = {
|
||||
@@ -38,6 +42,8 @@ export function JSONEditor(opts: JSONEditorProps) {
|
||||
onBlur,
|
||||
basicSetup,
|
||||
autoFocus,
|
||||
showCopyButton = true,
|
||||
showClearButton = true,
|
||||
} = {
|
||||
...defaultProps,
|
||||
...opts,
|
||||
@@ -65,7 +71,8 @@ export function JSONEditor(opts: JSONEditorProps) {
|
||||
onChange,
|
||||
onUpdate,
|
||||
};
|
||||
const { setContainer, state } = useCodeMirror(settings);
|
||||
const { setContainer, view } = useCodeMirror(settings);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (editor.current) {
|
||||
@@ -75,24 +82,71 @@ export function JSONEditor(opts: JSONEditorProps) {
|
||||
|
||||
//if the defaultValue changes update the editor
|
||||
useEffect(() => {
|
||||
if (state !== undefined) {
|
||||
state.update({
|
||||
changes: { from: 0, to: state.doc.length, insert: defaultValue },
|
||||
if (view !== undefined) {
|
||||
if (view.state.doc.toString() === defaultValue) return;
|
||||
view.dispatch({
|
||||
changes: { from: 0, to: view.state.doc.length, insert: defaultValue },
|
||||
});
|
||||
}
|
||||
}, [defaultValue, state]);
|
||||
}, [defaultValue, view]);
|
||||
|
||||
const clear = useCallback(() => {
|
||||
if (view === undefined) return;
|
||||
view.dispatch({
|
||||
changes: { from: 0, to: view.state.doc.length, insert: undefined },
|
||||
});
|
||||
onChange?.("");
|
||||
}, [view]);
|
||||
|
||||
const copy = useCallback(() => {
|
||||
if (view === undefined) return;
|
||||
navigator.clipboard.writeText(view.state.doc.toString());
|
||||
setCopied(true);
|
||||
setTimeout(() => {
|
||||
setCopied(false);
|
||||
}, 1500);
|
||||
}, [view]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700",
|
||||
opts.className
|
||||
)}
|
||||
ref={editor}
|
||||
onBlur={() => {
|
||||
if (!onBlur) return;
|
||||
onBlur(editor.current?.textContent ?? "");
|
||||
}}
|
||||
/>
|
||||
<div className={cn(opts.className, "relative")}>
|
||||
<div
|
||||
className="h-full w-full"
|
||||
ref={editor}
|
||||
onBlur={() => {
|
||||
if (!onBlur) return;
|
||||
onBlur(editor.current?.textContent ?? "");
|
||||
}}
|
||||
/>
|
||||
<div className="absolute right-3 top-3 flex items-center gap-2">
|
||||
{showClearButton && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary/small"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
clear();
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
)}
|
||||
{showCopyButton && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary/small"
|
||||
LeadingIcon={copied ? CheckIcon : ClipboardIcon}
|
||||
leadingIconClassName={copied ? "text-green-500 group-hover:text-green-500" : undefined}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
copy();
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,34 +1,19 @@
|
||||
import {
|
||||
highlightSpecialChars,
|
||||
drawSelection,
|
||||
highlightActiveLine,
|
||||
dropCursor,
|
||||
lineNumbers,
|
||||
highlightActiveLineGutter,
|
||||
keymap,
|
||||
} from "@codemirror/view";
|
||||
import type { Extension } from "@codemirror/state";
|
||||
import { highlightSelectionMatches } from "@codemirror/search";
|
||||
import { json as jsonLang } from "@codemirror/lang-json";
|
||||
import { closeBrackets } from "@codemirror/autocomplete";
|
||||
import { bracketMatching } from "@codemirror/language";
|
||||
import { indentWithTab } from "@codemirror/commands";
|
||||
|
||||
export function getPreviewSetup(): Array<Extension> {
|
||||
return [
|
||||
jsonLang(),
|
||||
highlightSpecialChars(),
|
||||
drawSelection(),
|
||||
dropCursor(),
|
||||
bracketMatching(),
|
||||
highlightSelectionMatches(),
|
||||
lineNumbers(),
|
||||
];
|
||||
}
|
||||
|
||||
export function getViewerSetup(): Array<Extension> {
|
||||
return [drawSelection(), dropCursor(), bracketMatching(), lineNumbers()];
|
||||
}
|
||||
import { jsonParseLinter } from "@codemirror/lang-json";
|
||||
import { bracketMatching } from "@codemirror/language";
|
||||
import { lintGutter, lintKeymap, linter } from "@codemirror/lint";
|
||||
import { highlightSelectionMatches } from "@codemirror/search";
|
||||
import { Prec, type Extension } from "@codemirror/state";
|
||||
import {
|
||||
drawSelection,
|
||||
dropCursor,
|
||||
highlightActiveLine,
|
||||
highlightActiveLineGutter,
|
||||
highlightSpecialChars,
|
||||
keymap,
|
||||
lineNumbers,
|
||||
} from "@codemirror/view";
|
||||
|
||||
export function getEditorSetup(showLineNumbers = true, showHighlights = true): Array<Extension> {
|
||||
const options = [
|
||||
@@ -36,7 +21,20 @@ export function getEditorSetup(showLineNumbers = true, showHighlights = true): A
|
||||
dropCursor(),
|
||||
bracketMatching(),
|
||||
closeBrackets(),
|
||||
keymap.of([indentWithTab]),
|
||||
lintGutter(),
|
||||
linter(jsonParseLinter()),
|
||||
Prec.highest(
|
||||
keymap.of([
|
||||
{
|
||||
key: "Mod-Enter",
|
||||
run: () => {
|
||||
return true;
|
||||
},
|
||||
preventDefault: false,
|
||||
},
|
||||
])
|
||||
),
|
||||
keymap.of([indentWithTab, ...lintKeymap]),
|
||||
];
|
||||
|
||||
if (showLineNumbers) {
|
||||
|
||||
@@ -17,10 +17,15 @@ export function darkTheme(): Extension {
|
||||
violet = "#c678dd",
|
||||
darkBackground = "#21252b",
|
||||
highlightBackground = "rgba(71,85,105,0.2)",
|
||||
background = "#0f172a",
|
||||
background = "rgba(11, 16, 24 ,100)",
|
||||
tooltipBackground = "#353a42",
|
||||
selection = "rgb(71 85 105)",
|
||||
cursor = "#528bff";
|
||||
cursor = "#528bff",
|
||||
scrollbarTrack = "#0E1521",
|
||||
scrollbarTrackActive = "#131B2B",
|
||||
scrollbarThumb = "#293649",
|
||||
scrollbarThumbActive = "#3C4B62",
|
||||
scrollbarBg = "#0E1521";
|
||||
|
||||
const jsonHeroEditorTheme = EditorView.theme(
|
||||
{
|
||||
@@ -94,6 +99,45 @@ export function darkTheme(): Extension {
|
||||
color: ivory,
|
||||
},
|
||||
},
|
||||
".cm-scroller": {
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: `${scrollbarThumb} ${scrollbarTrack}`,
|
||||
},
|
||||
".cm-scroller::-webkit-scrollbar": {
|
||||
display: "block",
|
||||
width: "8px",
|
||||
height: "8px",
|
||||
},
|
||||
".cm-scroller::-webkit-scrollbar-track": {
|
||||
backgroundColor: scrollbarTrack,
|
||||
borderRadius: "0",
|
||||
},
|
||||
".cm-scroller::-webkit-scrollbar-track:hover": {
|
||||
backgroundColor: scrollbarTrackActive,
|
||||
},
|
||||
".cm-scroller::-webkit-scrollbar-track:active": {
|
||||
backgroundColor: scrollbarTrackActive,
|
||||
},
|
||||
".cm-scroller::-webkit-scrollbar-thumb": {
|
||||
backgroundColor: scrollbarThumb,
|
||||
borderRadius: "0",
|
||||
},
|
||||
".cm-scroller::-webkit-scrollbar-thumb:hover": {
|
||||
backgroundColor: scrollbarThumbActive,
|
||||
},
|
||||
".cm-scroller::-webkit-scrollbar-thumb:active": {
|
||||
backgroundColor: scrollbarThumbActive,
|
||||
},
|
||||
".cm-scroller::-webkit-scrollbar-corner": {
|
||||
backgroundColor: scrollbarBg,
|
||||
borderRadius: "0",
|
||||
},
|
||||
".cm-scroller::-webkit-scrollbar-corner:hover": {
|
||||
backgroundColor: scrollbarBg,
|
||||
},
|
||||
".cm-scroller::-webkit-scrollbar-corner:active": {
|
||||
backgroundColor: scrollbarBg,
|
||||
},
|
||||
},
|
||||
{ dark: true }
|
||||
);
|
||||
@@ -155,157 +199,3 @@ export function darkTheme(): Extension {
|
||||
|
||||
return [jsonHeroEditorTheme, syntaxHighlighting(jsonHeroHighlightStyle)];
|
||||
}
|
||||
|
||||
export function lightTheme(): Extension[] {
|
||||
const stringColor = "text-[#53a053]",
|
||||
numberColor = "text-[#447bef]",
|
||||
variableColor = "text-[#a42ea2]",
|
||||
booleanColor = "text-[#e2574e]",
|
||||
coral = "text-[#e06c75]",
|
||||
invalid = "text-[#ffffff]",
|
||||
ivory = "text-[#abb2bf]",
|
||||
stone = "text-[#7d8799]",
|
||||
malibu = "text-[#61afef]",
|
||||
whiskey = "text-[#d19a66]",
|
||||
violet = "text-[#c678dd]",
|
||||
darkBackground = "text-[#21252b]",
|
||||
highlightBackground = "text-[#D0D0D0]",
|
||||
background = "text-[#ffffff]",
|
||||
tooltipBackground = "text-[#353a42]",
|
||||
selection = "text-[#D0D0D0]",
|
||||
cursor = "text-[#528bff]";
|
||||
|
||||
const jsonHeroEditorTheme = EditorView.theme(
|
||||
{
|
||||
"&": {
|
||||
color: ivory,
|
||||
backgroundColor: background,
|
||||
},
|
||||
|
||||
".cm-content": {
|
||||
caretColor: cursor,
|
||||
fontFamily: "monospace",
|
||||
fontSize: "14px",
|
||||
},
|
||||
|
||||
".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
|
||||
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
|
||||
backgroundColor: selection,
|
||||
},
|
||||
|
||||
".cm-panels": { backgroundColor: darkBackground, color: ivory },
|
||||
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
|
||||
".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
|
||||
|
||||
".cm-searchMatch": {
|
||||
backgroundColor: "#72a1ff59",
|
||||
outline: "1px solid #457dff",
|
||||
},
|
||||
".cm-searchMatch.cm-searchMatch-selected": {
|
||||
backgroundColor: "#6199ff2f",
|
||||
},
|
||||
|
||||
".cm-activeLine": { backgroundColor: highlightBackground },
|
||||
".cm-selectionMatch": { backgroundColor: "#aafe661a" },
|
||||
|
||||
"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
|
||||
backgroundColor: "#bad0f847",
|
||||
outline: "1px solid #515a6b",
|
||||
},
|
||||
|
||||
".cm-gutters": {
|
||||
backgroundColor: background,
|
||||
color: stone,
|
||||
border: "none",
|
||||
},
|
||||
|
||||
".cm-activeLineGutter": {
|
||||
backgroundColor: highlightBackground,
|
||||
},
|
||||
|
||||
".cm-foldPlaceholder": {
|
||||
backgroundColor: "transparent",
|
||||
border: "none",
|
||||
color: "#ddd",
|
||||
},
|
||||
|
||||
".cm-tooltip": {
|
||||
border: "none",
|
||||
backgroundColor: tooltipBackground,
|
||||
},
|
||||
".cm-tooltip .cm-tooltip-arrow:before": {
|
||||
borderTopColor: "transparent",
|
||||
borderBottomColor: "transparent",
|
||||
},
|
||||
".cm-tooltip .cm-tooltip-arrow:after": {
|
||||
borderTopColor: tooltipBackground,
|
||||
borderBottomColor: tooltipBackground,
|
||||
},
|
||||
".cm-tooltip-autocomplete": {
|
||||
"& > ul > li[aria-selected]": {
|
||||
backgroundColor: highlightBackground,
|
||||
color: ivory,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ dark: false }
|
||||
);
|
||||
|
||||
/// The highlighting style for code in the JSON Hero theme.
|
||||
const jsonHeroHighlightStyle = tagHighlighter([
|
||||
{ tag: tags.keyword, class: violet },
|
||||
{
|
||||
tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName],
|
||||
class: variableColor,
|
||||
},
|
||||
{
|
||||
tag: [tags.function(tags.variableName), tags.labelName],
|
||||
class: malibu,
|
||||
},
|
||||
{
|
||||
tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)],
|
||||
class: whiskey,
|
||||
},
|
||||
{ tag: [tags.definition(tags.name), tags.separator], class: ivory },
|
||||
{
|
||||
tag: [
|
||||
tags.typeName,
|
||||
tags.className,
|
||||
tags.number,
|
||||
tags.changed,
|
||||
tags.annotation,
|
||||
tags.modifier,
|
||||
tags.self,
|
||||
tags.namespace,
|
||||
],
|
||||
class: numberColor,
|
||||
},
|
||||
{
|
||||
tag: [
|
||||
tags.operator,
|
||||
tags.operatorKeyword,
|
||||
tags.url,
|
||||
tags.escape,
|
||||
tags.regexp,
|
||||
tags.link,
|
||||
tags.special(tags.string),
|
||||
],
|
||||
class: stringColor,
|
||||
},
|
||||
{ tag: [tags.meta, tags.comment], class: stone },
|
||||
|
||||
{ tag: tags.link, class: stone },
|
||||
{ tag: tags.heading, class: coral },
|
||||
{
|
||||
tag: [tags.atom, tags.bool, tags.special(tags.variableName)],
|
||||
class: booleanColor,
|
||||
},
|
||||
{
|
||||
tag: [tags.processingInstruction, tags.string, tags.inserted],
|
||||
class: stringColor,
|
||||
},
|
||||
{ tag: tags.invalid, class: invalid },
|
||||
]);
|
||||
|
||||
return [jsonHeroEditorTheme, syntaxHighlighting(jsonHeroHighlightStyle)];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { CheckCircleIcon, ClockIcon, XCircleIcon } from "@heroicons/react/20/solid";
|
||||
import { EndpointIndexStatus } from "@trigger.dev/database";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Spinner } from "../primitives/Spinner";
|
||||
|
||||
export function EndpointIndexStatusIcon({ status }: { status: EndpointIndexStatus }) {
|
||||
switch (status) {
|
||||
case "PENDING":
|
||||
return <ClockIcon className={cn("h-4 w-4", endpointIndexStatusClassNameColor(status))} />;
|
||||
case "STARTED":
|
||||
return <Spinner className={cn("h-4 w-4", endpointIndexStatusClassNameColor(status))} />;
|
||||
case "SUCCESS":
|
||||
return (
|
||||
<CheckCircleIcon className={cn("h-4 w-4", endpointIndexStatusClassNameColor(status))} />
|
||||
);
|
||||
case "FAILURE":
|
||||
return <XCircleIcon className={cn("h-4 w-4", endpointIndexStatusClassNameColor(status))} />;
|
||||
}
|
||||
}
|
||||
|
||||
export function EndpointIndexStatusLabel({ status }: { status: EndpointIndexStatus }) {
|
||||
switch (status) {
|
||||
case "PENDING":
|
||||
return (
|
||||
<span className={endpointIndexStatusClassNameColor(status)}>
|
||||
{endpointIndexStatusTitle(status)}
|
||||
</span>
|
||||
);
|
||||
case "STARTED":
|
||||
return (
|
||||
<span className={endpointIndexStatusClassNameColor(status)}>
|
||||
{endpointIndexStatusTitle(status)}
|
||||
</span>
|
||||
);
|
||||
case "SUCCESS":
|
||||
return (
|
||||
<span className={endpointIndexStatusClassNameColor(status)}>
|
||||
{endpointIndexStatusTitle(status)}
|
||||
</span>
|
||||
);
|
||||
case "FAILURE":
|
||||
return (
|
||||
<span className={endpointIndexStatusClassNameColor(status)}>
|
||||
{endpointIndexStatusTitle(status)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function endpointIndexStatusTitle(status: EndpointIndexStatus): string {
|
||||
switch (status) {
|
||||
case "PENDING":
|
||||
return "Pending";
|
||||
case "STARTED":
|
||||
return "Started";
|
||||
case "SUCCESS":
|
||||
return "Success";
|
||||
case "FAILURE":
|
||||
return "Failure";
|
||||
}
|
||||
}
|
||||
|
||||
export function endpointIndexStatusClassNameColor(status: EndpointIndexStatus): string {
|
||||
switch (status) {
|
||||
case "PENDING":
|
||||
return "text-dimmed";
|
||||
case "STARTED":
|
||||
return "text-blue-500";
|
||||
case "SUCCESS":
|
||||
return "text-green-500";
|
||||
case "FAILURE":
|
||||
return "text-rose-500";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { ArrowPathIcon } from "@heroicons/react/20/solid";
|
||||
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
|
||||
import { useFetcher } from "@remix-run/react";
|
||||
import { useState } from "react";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "~/components/primitives/Dialog";
|
||||
import { generateTwoRandomWords } from "~/utils/randomWords";
|
||||
import { Button } from "../primitives/Buttons";
|
||||
import { Header1 } from "../primitives/Headers";
|
||||
import { Input } from "../primitives/Input";
|
||||
import { Paragraph } from "../primitives/Paragraph";
|
||||
import { Spinner } from "../primitives/Spinner";
|
||||
|
||||
type ModalProps = {
|
||||
id: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
type ModalContentProps = ModalProps & {
|
||||
randomWord: string;
|
||||
closeModal: () => void;
|
||||
};
|
||||
|
||||
export function RegenerateApiKeyModal({ id, title }: ModalProps) {
|
||||
const randomWord = generateTwoRandomWords();
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
variant="tertiary/small"
|
||||
leadingIconClassName="text-dimmed"
|
||||
LeadingIcon={ArrowPathIcon}
|
||||
>
|
||||
Regenerate
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>{`Regenerate ${title} Environment Key`}</DialogHeader>
|
||||
<RegenerateApiKeyModalContent
|
||||
id={id}
|
||||
title={title}
|
||||
randomWord={randomWord}
|
||||
closeModal={() => setOpen(false)}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
const RegenerateApiKeyModalContent = ({ id, randomWord, title, closeModal }: ModalContentProps) => {
|
||||
const [confirmationText, setConfirmationText] = useState("");
|
||||
const fetcher = useFetcher();
|
||||
const isSubmitting = fetcher.state === "submitting";
|
||||
|
||||
// form submission completed
|
||||
if (fetcher.state === "loading") {
|
||||
closeModal();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col items-center gap-y-5 pb-4">
|
||||
<div className="mt-3 flex gap-x-3 rounded-md border border-ui-border py-4 pl-3 pr-5">
|
||||
<ExclamationTriangleIcon className="relative top-1 h-6 w-6 min-w-[2rem] text-amber-500" />
|
||||
<Paragraph>
|
||||
Regenerating the keys for this environment will temporarily break any live Jobs in the
|
||||
<span className="text-bright"> {title} Environment</span> until the new API keys are set
|
||||
in the relevant environment variables.
|
||||
</Paragraph>
|
||||
</div>
|
||||
<fetcher.Form
|
||||
method="post"
|
||||
action={`/resources/environments/${id}/regenerate-api-key`}
|
||||
className="mt-2 flex w-full flex-col items-center gap-2"
|
||||
>
|
||||
<div className="mb-4 flex items-center gap-x-2">
|
||||
<Paragraph variant="small/bright">Enter this text below to confirm:</Paragraph>
|
||||
<Paragraph
|
||||
variant="small"
|
||||
className="select-all rounded-md border border-ui-border bg-slate-900 px-2 py-1 font-mono text-bright"
|
||||
>
|
||||
{randomWord}
|
||||
</Paragraph>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Confirmation text"
|
||||
fullWidth
|
||||
value={confirmationText}
|
||||
onChange={(e) => setConfirmationText(e.target.value)}
|
||||
className="rounded-r-none"
|
||||
variant="large"
|
||||
/>
|
||||
<Button
|
||||
variant="primary/large"
|
||||
disabled={confirmationText !== randomWord}
|
||||
className="rounded-l-none"
|
||||
>
|
||||
{isSubmitting ? <Spinner color="white" /> : <>Regenerate</>}
|
||||
</Button>
|
||||
</div>
|
||||
</fetcher.Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Squares2X2Icon } from "@heroicons/react/20/solid";
|
||||
import { GitHubDarkIcon } from "@trigger.dev/companyicons";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { projectSetupPath } from "~/utils/pathBuilder";
|
||||
import { LinkButton } from "../primitives/Buttons";
|
||||
import { Header1 } from "../primitives/Headers";
|
||||
import { NamedIcon } from "../primitives/NamedIcon";
|
||||
import { Paragraph } from "../primitives/Paragraph";
|
||||
export type FrameworkComingSoonProps = {
|
||||
frameworkName: string;
|
||||
githubIssueUrl: string;
|
||||
githubIssueNumber: number;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export function FrameworkComingSoon({
|
||||
frameworkName,
|
||||
githubIssueUrl,
|
||||
githubIssueNumber,
|
||||
children,
|
||||
}: FrameworkComingSoonProps) {
|
||||
const organization = useOrganization();
|
||||
const project = useProject();
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-3xl pt-16">
|
||||
<div className="mb-6 grid place-items-center pb-6">{children}</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Header1 spacing>{frameworkName} is coming soon!</Header1>
|
||||
<LinkButton
|
||||
to={projectSetupPath(organization, project)}
|
||||
variant="tertiary/small"
|
||||
LeadingIcon={Squares2X2Icon}
|
||||
>
|
||||
Choose a different framework
|
||||
</LinkButton>
|
||||
</div>
|
||||
<Paragraph spacing className="border-b border-slate-800 pb-4">
|
||||
We're working hard to bring support for {frameworkName} in Trigger.dev. Follow along with
|
||||
the GitHub issue or contribute and help us bring it to Trigger.dev faster.
|
||||
</Paragraph>
|
||||
<a
|
||||
href={githubIssueUrl}
|
||||
target="_blank"
|
||||
className="group mt-4 block max-w-sm rounded-md bg-bright px-10 py-8 transition hover:bg-slate-100"
|
||||
>
|
||||
<Paragraph spacing>triggerdotdev/trigger.dev</Paragraph>
|
||||
<h2 className="text-2xl font-semibold text-black">
|
||||
<span className="mr-1 font-normal text-dimmed">#{githubIssueNumber}</span>Framework:
|
||||
support for {frameworkName}
|
||||
</h2>
|
||||
<div className="mt-4 flex items-center gap-1.5">
|
||||
<GitHubDarkIcon className="h-4 w-4" />
|
||||
<Paragraph variant="small">View on GitHub</Paragraph>
|
||||
<NamedIcon
|
||||
name="arrow-right"
|
||||
className="h-4 w-4 text-dimmed transition group-hover:translate-x-1"
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { ChatBubbleLeftRightIcon } from "@heroicons/react/20/solid";
|
||||
import { Link } from "@remix-run/react";
|
||||
import { AstroLogo } from "~/assets/logos/AstroLogo";
|
||||
import { ExpressLogo } from "~/assets/logos/ExpressLogo";
|
||||
import { FastifyLogo } from "~/assets/logos/FastifyLogo";
|
||||
import { NestjsLogo } from "~/assets/logos/NestjsLogo";
|
||||
import { NextjsLogo } from "~/assets/logos/NextjsLogo";
|
||||
import { NuxtLogo } from "~/assets/logos/NuxtLogo";
|
||||
import { RedwoodLogo } from "~/assets/logos/RedwoodLogo";
|
||||
import { RemixLogo } from "~/assets/logos/RemixLogo";
|
||||
import { SvelteKitLogo } from "~/assets/logos/SveltekitLogo";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { cn } from "~/utils/cn";
|
||||
import {
|
||||
projectSetupAstroPath,
|
||||
projectSetupExpressPath,
|
||||
projectSetupFastifyPath,
|
||||
projectSetupNestjsPath,
|
||||
projectSetupNextjsPath,
|
||||
projectSetupNuxtPath,
|
||||
projectSetupRedwoodPath,
|
||||
projectSetupRemixPath,
|
||||
projectSetupSvelteKitPath,
|
||||
} from "~/utils/pathBuilder";
|
||||
import { Feedback } from "../Feedback";
|
||||
import { Button } from "../primitives/Buttons";
|
||||
import { Header1 } from "../primitives/Headers";
|
||||
|
||||
export function FrameworkSelector() {
|
||||
const organization = useOrganization();
|
||||
const project = useProject();
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-3xl pt-28">
|
||||
<div className="flex items-center justify-between">
|
||||
<Header1 spacing>Choose a framework to get started…</Header1>
|
||||
<Feedback
|
||||
button={
|
||||
<Button variant="tertiary/small" LeadingIcon={ChatBubbleLeftRightIcon}>
|
||||
Request a framework
|
||||
</Button>
|
||||
}
|
||||
defaultValue="feature"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 place-items-center gap-2">
|
||||
<FrameworkLink to={projectSetupNextjsPath(organization, project)} supported>
|
||||
<NextjsLogo className="w-32" />
|
||||
</FrameworkLink>
|
||||
<FrameworkLink to={projectSetupExpressPath(organization, project)} supported>
|
||||
<ExpressLogo className="w-36" />
|
||||
</FrameworkLink>
|
||||
<FrameworkLink to={projectSetupRemixPath(organization, project)} supported>
|
||||
<RemixLogo className="w-32" />
|
||||
</FrameworkLink>
|
||||
<FrameworkLink to={projectSetupAstroPath(organization, project)} supported>
|
||||
<AstroLogo className="w-32" />
|
||||
</FrameworkLink>
|
||||
<FrameworkLink to={projectSetupSvelteKitPath(organization, project)} supported>
|
||||
<SvelteKitLogo className="w-44" />
|
||||
</FrameworkLink>
|
||||
<FrameworkLink to={projectSetupNestjsPath(organization, project)} supported>
|
||||
<NestjsLogo className="w-36" />
|
||||
</FrameworkLink>
|
||||
<FrameworkLink to={projectSetupFastifyPath(organization, project)}>
|
||||
<FastifyLogo className="w-36" />
|
||||
</FrameworkLink>
|
||||
<FrameworkLink to={projectSetupNuxtPath(organization, project)}>
|
||||
<NuxtLogo className="w-32" />
|
||||
</FrameworkLink>
|
||||
<FrameworkLink to={projectSetupRedwoodPath(organization, project)}>
|
||||
<RedwoodLogo className="w-44" />
|
||||
</FrameworkLink>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type FrameworkLinkProps = {
|
||||
children: React.ReactNode;
|
||||
to: string;
|
||||
supported?: boolean;
|
||||
};
|
||||
|
||||
function FrameworkLink({ children, to, supported = false }: FrameworkLinkProps) {
|
||||
return (
|
||||
<Link
|
||||
to={to}
|
||||
className={cn(
|
||||
"flex h-28 w-full items-center justify-center rounded-md border border-slate-750 px-8 py-4 transition hover:bg-slate-850",
|
||||
!supported && "border opacity-30 grayscale transition hover:opacity-100 hover:grayscale-0"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -1,268 +1,26 @@
|
||||
import { ChatBubbleLeftRightIcon } from "@heroicons/react/20/solid";
|
||||
import { Link, useRevalidator } from "@remix-run/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEventSource } from "remix-utils";
|
||||
import invariant from "tiny-invariant";
|
||||
import gradientBackground from "~/assets/images/gradient-background.png";
|
||||
import { Link } from "@remix-run/react";
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
import { StepNumber } from "~/components/primitives/StepNumber";
|
||||
import { useAppOrigin } from "~/hooks/useAppOrigin";
|
||||
import { useDevEnvironment } from "~/hooks/useEnvironments";
|
||||
import { useJob } from "~/hooks/useJob";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { IntegrationIcon } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations/route";
|
||||
import { jobTestPath, projectStreamingPath } from "~/utils/pathBuilder";
|
||||
import { Feedback } from "../Feedback";
|
||||
import { docsPath, jobTestPath } from "~/utils/pathBuilder";
|
||||
import { CodeBlock } from "../code/CodeBlock";
|
||||
import { InlineCode } from "../code/InlineCode";
|
||||
import { EnvironmentLabel } from "../environments/EnvironmentLabel";
|
||||
import { HelpPanelProps } from "../integrations/ApiKeyHelp";
|
||||
import { HelpInstall } from "../integrations/HelpInstall";
|
||||
import { HelpSamples } from "../integrations/HelpSamples";
|
||||
import { Button, LinkButton } from "../primitives/Buttons";
|
||||
import { LinkButton } from "../primitives/Buttons";
|
||||
import { Callout, variantClasses } from "../primitives/Callout";
|
||||
import {
|
||||
ClientTabs,
|
||||
ClientTabsContent,
|
||||
ClientTabsList,
|
||||
ClientTabsTrigger,
|
||||
} from "../primitives/ClientTabs";
|
||||
import { ClipboardField } from "../primitives/ClipboardField";
|
||||
import { Header1, Header2 } from "../primitives/Headers";
|
||||
import { NamedIcon } from "../primitives/NamedIcon";
|
||||
import { RadioGroup, RadioGroupItem } from "../primitives/RadioButton";
|
||||
import { Header2 } from "../primitives/Headers";
|
||||
import { TextLink } from "../primitives/TextLink";
|
||||
import integrationButton from "./integration-button.png";
|
||||
import selectEnvironment from "./select-environment.png";
|
||||
import selectExample from "./select-example.png";
|
||||
|
||||
type SelectionChoices = "use-existing-project" | "create-new-next-app";
|
||||
|
||||
export function HowToSetupYourProject() {
|
||||
const project = useProject();
|
||||
const devEnvironment = useDevEnvironment();
|
||||
const appOrigin = useAppOrigin();
|
||||
|
||||
const [selectedValue, setSelectedValue] = useState<SelectionChoices | null>(null);
|
||||
|
||||
invariant(devEnvironment, "devEnvironment is required");
|
||||
|
||||
const revalidator = useRevalidator();
|
||||
const events = useEventSource(projectStreamingPath(project.id), {
|
||||
event: "message",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (events !== null) {
|
||||
// This uses https://www.npmjs.com/package/canvas-confetti
|
||||
if ("confetti" in window && typeof window.confetti !== "undefined") {
|
||||
const duration = 3.5 * 1000;
|
||||
const animationEnd = Date.now() + duration;
|
||||
const defaults = {
|
||||
startVelocity: 30,
|
||||
spread: 360,
|
||||
ticks: 60,
|
||||
zIndex: 0,
|
||||
colors: [
|
||||
"#E7FF52",
|
||||
"#41FF54",
|
||||
"rgb(245 158 11)",
|
||||
"rgb(22 163 74)",
|
||||
"rgb(37 99 235)",
|
||||
"rgb(67 56 202)",
|
||||
"rgb(219 39 119)",
|
||||
"rgb(225 29 72)",
|
||||
"rgb(217 70 239)",
|
||||
],
|
||||
};
|
||||
function randomInRange(min: number, max: number): number {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
// @ts-ignore
|
||||
const interval = setInterval(function () {
|
||||
const timeLeft = animationEnd - Date.now();
|
||||
|
||||
if (timeLeft <= 0) {
|
||||
return clearInterval(interval);
|
||||
}
|
||||
|
||||
const particleCount = 50 * (timeLeft / duration);
|
||||
// since particles fall down, start a bit higher than random
|
||||
// @ts-ignore
|
||||
window.confetti(
|
||||
Object.assign({}, defaults, {
|
||||
particleCount,
|
||||
origin: { x: randomInRange(0.1, 0.4), y: Math.random() - 0.2 },
|
||||
})
|
||||
);
|
||||
// @ts-ignore
|
||||
window.confetti(
|
||||
Object.assign({}, defaults, {
|
||||
particleCount,
|
||||
origin: { x: randomInRange(0.6, 0.9), y: Math.random() - 0.2 },
|
||||
})
|
||||
);
|
||||
}, 250);
|
||||
}
|
||||
|
||||
revalidator.revalidate();
|
||||
}
|
||||
// WARNING Don't put the revalidator in the useEffect deps array or bad things will happen
|
||||
}, [events]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return (
|
||||
<div
|
||||
className="-ml-4 -mt-4 h-full w-[calc(100%+32px)] bg-cover bg-no-repeat pt-20"
|
||||
style={{ backgroundImage: `url("${gradientBackground}")` }}
|
||||
>
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<div className="flex items-center justify-between">
|
||||
<Header1 spacing className="text-bright">
|
||||
Get setup in {selectedValue === "create-new-next-app" ? "5" : "2"} minutes
|
||||
</Header1>
|
||||
<Feedback
|
||||
button={
|
||||
<Button variant="secondary/small" LeadingIcon={ChatBubbleLeftRightIcon}>
|
||||
I'm stuck!
|
||||
</Button>
|
||||
}
|
||||
defaultValue="help"
|
||||
/>
|
||||
</div>
|
||||
<RadioGroup
|
||||
className="mb-4 flex gap-x-2"
|
||||
onValueChange={(value) => setSelectedValue(value as SelectionChoices)}
|
||||
>
|
||||
<RadioGroupItem
|
||||
label="Use an existing Next.js project"
|
||||
description="Use Trigger.dev in an existing Next.js project in less than 2 mins."
|
||||
value="use-existing-project"
|
||||
checked={selectedValue === "use-existing-project"}
|
||||
variant="icon"
|
||||
data-action="use-existing-project"
|
||||
icon={<NamedIcon className="h-12 w-12 text-green-600" name={"tree"} />}
|
||||
/>
|
||||
<RadioGroupItem
|
||||
label="Create a new Next.js project"
|
||||
description="This is the quickest way to try out Trigger.dev in a new Next.js project and takes 5 mins."
|
||||
value="create-new-next-app"
|
||||
checked={selectedValue === "create-new-next-app"}
|
||||
variant="icon"
|
||||
data-action="create-new-next-app"
|
||||
icon={<NamedIcon className="h-8 w-8 text-green-600" name={"sapling"} />}
|
||||
/>
|
||||
</RadioGroup>
|
||||
{selectedValue && (
|
||||
<>
|
||||
{selectedValue === "create-new-next-app" ? (
|
||||
<>
|
||||
<StepNumber stepNumber="1" title="Create a new Next.js project" />
|
||||
<StepContentContainer>
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`npx create-next-app@latest`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`pnpm create next-app`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`yarn create next-app`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
|
||||
<Paragraph spacing variant="small">
|
||||
Trigger.dev works with either the Pages or App Router configuration.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="2" title="Navigate to your new Next.js project" />
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>
|
||||
You have now created a new Next.js project. Let’s <InlineCode>cd</InlineCode>{" "}
|
||||
into it using the project name you just provided:
|
||||
</Paragraph>
|
||||
<ClipboardField
|
||||
value={"cd [replace with your project name]"}
|
||||
variant={"primary/medium"}
|
||||
></ClipboardField>
|
||||
</StepContentContainer>
|
||||
<StepNumber
|
||||
stepNumber="3"
|
||||
title="Run the CLI 'init' command in your new Next.js project"
|
||||
/>
|
||||
<StepContentContainer>
|
||||
<InitCommand appOrigin={appOrigin} apiKey={devEnvironment.apiKey} />
|
||||
<Paragraph spacing variant="small">
|
||||
You’ll notice a new folder in your project called 'jobs'. We’ve added a very
|
||||
simple example Job in <InlineCode variant="extra-small">examples.ts</InlineCode>{" "}
|
||||
to help you get started.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="4" title="Run your Next.js app" />
|
||||
<StepContentContainer>
|
||||
<NextDevCommand />
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="5" title="Run the CLI 'dev' command" />
|
||||
<StepContentContainer>
|
||||
<TriggerDevStep />
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="6" title="Wait for Jobs" displaySpinner />
|
||||
<StepContentContainer>
|
||||
<Paragraph>This page will automatically refresh.</Paragraph>
|
||||
</StepContentContainer>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<StepNumber
|
||||
stepNumber="1"
|
||||
title="Run the CLI 'init' command in an existing Next.js project"
|
||||
/>
|
||||
<StepContentContainer>
|
||||
<InitCommand appOrigin={appOrigin} apiKey={devEnvironment.apiKey} />
|
||||
|
||||
<Paragraph spacing variant="small">
|
||||
You’ll notice a new folder in your project called 'jobs'. We’ve added a very
|
||||
simple example Job in <InlineCode variant="extra-small">examples.ts</InlineCode>{" "}
|
||||
to help you get started.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="2" title="Run your Next.js app" />
|
||||
<StepContentContainer>
|
||||
<NextDevCommand />
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="3" title="Run the CLI 'dev' command" />
|
||||
<StepContentContainer>
|
||||
<TriggerDevStep />
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="4" title="Wait for Jobs" displaySpinner />
|
||||
<StepContentContainer>
|
||||
<Paragraph>This page will automatically refresh.</Paragraph>
|
||||
</StepContentContainer>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import { StepContentContainer } from "../StepContentContainer";
|
||||
import { TriggerDevCommand } from "../SetupCommands";
|
||||
import { IntegrationIcon } from "~/assets/icons/IntegrationIcon";
|
||||
|
||||
export function HowToRunYourJob() {
|
||||
const organization = useOrganization();
|
||||
@@ -271,20 +29,6 @@ export function HowToRunYourJob() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Callout variant="info" className="mb-6">
|
||||
<Paragraph variant={"small"} className={variantClasses.info.textColor}>
|
||||
Scheduled Triggers <strong>do not</strong> trigger Jobs in the DEV Environment. When
|
||||
developing locally you should use the{" "}
|
||||
<Link
|
||||
to={jobTestPath(organization, project, job)}
|
||||
className="underline underline-offset-2 transition hover:text-blue-100"
|
||||
>
|
||||
Test feature
|
||||
</Link>{" "}
|
||||
to trigger any scheduled Jobs.
|
||||
</Paragraph>
|
||||
</Callout>
|
||||
|
||||
<Paragraph spacing>There are two ways to run your Job:</Paragraph>
|
||||
|
||||
<StepNumber stepNumber="1" title="Trigger a test Run" />
|
||||
@@ -318,36 +62,18 @@ export function HowToRunYourJob() {
|
||||
How to run a Job
|
||||
</LinkButton>
|
||||
</StepContentContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function HowToRunATest() {
|
||||
return (
|
||||
<>
|
||||
<StepNumber
|
||||
stepNumber="1"
|
||||
title="Select an environment
|
||||
"
|
||||
/>
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>Select the environment you’d like the test to run against.</Paragraph>
|
||||
<img src={selectEnvironment} className="mt-2 w-52" />
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="2" title="Write your test payload" />
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>
|
||||
Write your own payload specific to your Job. Some Triggers also provide example payloads
|
||||
that you can select from. This will populate the code editor below.
|
||||
<Callout variant="info">
|
||||
<Paragraph variant={"small"} className={variantClasses.info.textColor}>
|
||||
Scheduled Triggers <strong>do not</strong> trigger Jobs in the DEV Environment. When
|
||||
developing locally you should use the{" "}
|
||||
<Link
|
||||
to={jobTestPath(organization, project, job)}
|
||||
className="underline underline-offset-2 transition hover:text-blue-100"
|
||||
>
|
||||
Test feature
|
||||
</Link>{" "}
|
||||
to trigger any scheduled Jobs.
|
||||
</Paragraph>
|
||||
<img src={selectExample} className="mt-2 h-40" />
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="3" title="Run your test" />
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>When you’re happy with the payload, click Run test.</Paragraph>
|
||||
</StepContentContainer>
|
||||
<Callout variant="docs" to="https://trigger.dev/docs/documentation/guides/testing-jobs">
|
||||
Learn more about running tests.
|
||||
</Callout>
|
||||
</>
|
||||
);
|
||||
@@ -515,6 +241,21 @@ export function HowToUseApiKeysAndEndpoints() {
|
||||
you should use the Test feature to trigger any scheduled Jobs.
|
||||
</Callout>
|
||||
</StepContentContainer>
|
||||
<StepNumber
|
||||
stepNumber="→"
|
||||
title={
|
||||
<span className="flex items-center gap-x-2">
|
||||
<span>Staging</span>
|
||||
<EnvironmentLabel environment={{ type: "STAGING" }} />
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>
|
||||
The <InlineCode>STAGING</InlineCode> environment is where your Jobs will run in a staging
|
||||
environment, meant to mirror your production environment.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
<StepNumber
|
||||
stepNumber="→"
|
||||
title={
|
||||
@@ -554,115 +295,139 @@ export function HowToUseApiKeysAndEndpoints() {
|
||||
);
|
||||
}
|
||||
|
||||
function StepContentContainer({ children }: { children: React.ReactNode }) {
|
||||
return <div className="mb-6 ml-9 mt-1">{children}</div>;
|
||||
}
|
||||
|
||||
function InitCommand({ appOrigin, apiKey }: { appOrigin: string; apiKey: string }) {
|
||||
return (
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
secure={`npx @trigger.dev/cli@latest init -k ••••••••• -t ${appOrigin}`}
|
||||
value={`npx @trigger.dev/cli@latest init -k ${apiKey} -t ${appOrigin}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
secure={`pnpm dlx @trigger.dev/cli@latest init -k ••••••••• -t ${appOrigin}`}
|
||||
value={`pnpm dlx @trigger.dev/cli@latest init -k ${apiKey} -t ${appOrigin}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
secure={`yarn dlx @trigger.dev/cli@latest init -k ••••••••• -t ${appOrigin}`}
|
||||
value={`yarn dlx @trigger.dev/cli@latest init -k ${apiKey} -t ${appOrigin}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
);
|
||||
}
|
||||
|
||||
function NextDevCommand() {
|
||||
return (
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField variant="primary/medium" className="mb-4" value={`npm run dev`} />
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField variant="primary/medium" className="mb-4" value={`pnpm run dev`} />
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField variant="primary/medium" className="mb-4" value={`yarn run dev`} />
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
);
|
||||
}
|
||||
|
||||
function TriggerDevCommand() {
|
||||
return (
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`npx @trigger.dev/cli@latest dev`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`pnpm dlx @trigger.dev/cli@latest dev`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
className="mb-4"
|
||||
value={`yarn dlx @trigger.dev/cli@latest dev`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
);
|
||||
}
|
||||
|
||||
function TriggerDevStep() {
|
||||
export function WhatAreHttpEndpoints() {
|
||||
return (
|
||||
<>
|
||||
<Paragraph spacing>
|
||||
In a <span className="text-amber-400">separate terminal window or tab</span> run:
|
||||
HTTP endpoints allow you to trigger your Jobs from any webhooks. They require a bit more
|
||||
work than using <TextLink to={docsPath("integrations/introduction")}>Integrations</TextLink>{" "}
|
||||
but allow you to connect to any API.
|
||||
</Paragraph>
|
||||
<TriggerDevCommand />
|
||||
<Paragraph spacing variant="small">
|
||||
If you’re not running on port 3000 you can specify the port by adding{" "}
|
||||
<InlineCode variant="extra-small">--port 3001</InlineCode> to the end.
|
||||
<Header2 spacing>Getting started</Header2>
|
||||
<Paragraph spacing>
|
||||
You need to define the HTTP endpoint in your code. To do this you use{" "}
|
||||
<InlineCode>client.defineHttpEndpoint()</InlineCode>. This will create an HTTP endpoint.
|
||||
</Paragraph>
|
||||
<Paragraph spacing variant="small">
|
||||
You should leave the <InlineCode variant="extra-small">dev</InlineCode> command running when
|
||||
you're developing.
|
||||
<Paragraph spacing>
|
||||
Then you can create a Trigger from this by calling <InlineCode>.onRequest()</InlineCode> on
|
||||
the created HTTP endpoint.
|
||||
</Paragraph>
|
||||
<Callout variant="docs" to={docsPath("documentation/concepts/http-endpoints")}>
|
||||
Read the HTTP endpoints guide to learn more.
|
||||
</Callout>
|
||||
<Header2 spacing className="mt-4">
|
||||
An example: cal.com
|
||||
</Header2>
|
||||
<CodeBlock
|
||||
code={`//create an HTTP endpoint
|
||||
const caldotcom = client.defineHttpEndpoint({
|
||||
id: "cal.com",
|
||||
source: "cal.com",
|
||||
icon: "caldotcom",
|
||||
verify: async (request) => {
|
||||
//this helper function makes verifying most webhooks easy
|
||||
return await verifyRequestSignature({
|
||||
request,
|
||||
headerName: "X-Cal-Signature-256",
|
||||
secret: process.env.CALDOTCOM_SECRET!,
|
||||
algorithm: "sha256",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
client.defineJob({
|
||||
id: "http-caldotcom",
|
||||
name: "HTTP Cal.com",
|
||||
version: "1.0.0",
|
||||
enabled: true,
|
||||
//create a Trigger from the HTTP endpoint above. The filter is optional.
|
||||
trigger: caldotcom.onRequest({ filter: { body: { triggerEvent: ["BOOKING_CANCELLED"] } } }),
|
||||
run: async (request, io, ctx) => {
|
||||
//note that when using HTTP endpoints, the first parameter is the request
|
||||
//you need to get the body, usually it will be json so you do:
|
||||
const body = await request.json();
|
||||
await io.logger.info("Body", body);
|
||||
},
|
||||
});`}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function HowToConnectHttpEndpoint() {
|
||||
return (
|
||||
<>
|
||||
<Header2 spacing>Setting up your webhook</Header2>
|
||||
<Paragraph spacing>
|
||||
To start receiving data you need to enter the Endpoint URL and secret into the API service
|
||||
you want to receive webhooks from.
|
||||
</Paragraph>
|
||||
|
||||
<StepNumber stepNumber="1" title={<>Go to the relevant API dashboard</>} />
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>
|
||||
For example, if you want to receive webhooks from Cal.com then you should login to your
|
||||
Cal.com account and go to their Settings/Developer/Webhooks page.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
|
||||
<StepNumber stepNumber="2" title={<>Copy the Webhook URL and Secret</>} />
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>
|
||||
A unique Webhook URL is created for each environment (Dev, Staging, and Prod). Jobs will
|
||||
only be triggered from the relevant environment.
|
||||
</Paragraph>
|
||||
<Paragraph spacing>
|
||||
Copy the relevant Endpoint URL and secret from the table opposite and paste it into the
|
||||
correct place in the API dashboard you located in the previous step.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
|
||||
<StepNumber stepNumber="3" title={<>Add the Secret to your Environment variables</>} />
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>
|
||||
You should also add the Secret to the Environment variables in your code and where you're
|
||||
deploying. Usually in Node this means adding it to the .env file.
|
||||
</Paragraph>
|
||||
<Paragraph spacing>
|
||||
Use the secret in the <InlineCode>verify()</InlineCode> function of HTTP Endpoint. This
|
||||
ensures that someone can't just send a request to your Endpoint and trigger a Job.
|
||||
Different APIs do this verification in different ways – a common way is to have a header
|
||||
that has a hash of the payload and secret. Refer to the API's documentation for more
|
||||
information.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
|
||||
<Header2 spacing>Triggering runs</Header2>
|
||||
<StepNumber stepNumber="1" title="Ensure you're using the HTTP Endpoint in your code" />
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>
|
||||
In your code, you should use the <InlineCode>.onRequest()</InlineCode> function in a Job
|
||||
Trigger. You can filter so only data that matches your criteria triggers the Job.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="2" title="Make sure your code is deployed (for Staging and Prod)" />
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>
|
||||
If you're using the Staging or Prod environment, you need to make sure your code is
|
||||
deployed. Deploy like you normally would –{" "}
|
||||
<TextLink to={docsPath("documentation/guides/deployment")}>
|
||||
read our deployment guide
|
||||
</TextLink>
|
||||
.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="3" title="Perform an action that sends a webhook" />
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>
|
||||
Now you need to actually perform an action on that third-party service that triggers the
|
||||
webhook you've subscribed to. For example, add a new meeting using Cal.com.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
|
||||
<Callout variant="docs" to={docsPath("documentation/concepts/http-endpoints")}>
|
||||
Read the HTTP endpoints guide to learn more.
|
||||
</Callout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useState } from "react";
|
||||
import { ApiAuthenticationMethodApiKey, Integration } from "~/services/externalApis/types";
|
||||
import { docsIntegrationPath } from "~/utils/pathBuilder";
|
||||
import { Integration } from "~/services/externalApis/types";
|
||||
import { apiReferencePath, docsIntegrationPath } from "~/utils/pathBuilder";
|
||||
import { LinkButton } from "../primitives/Buttons";
|
||||
import { Header1, Header2 } from "../primitives/Headers";
|
||||
import { NamedIconInBox } from "../primitives/NamedIcon";
|
||||
@@ -48,6 +48,15 @@ export function ConnectToIntegrationSheet({
|
||||
<Paragraph variant="small">{integration.description}</Paragraph>
|
||||
)}
|
||||
</div>
|
||||
<LinkButton
|
||||
to={apiReferencePath(integration.identifier)}
|
||||
variant="secondary/small"
|
||||
TrailingIcon="arrow-up-right"
|
||||
trailingIconClassName="h-4 w-4 text-slate-400"
|
||||
target="_blank"
|
||||
>
|
||||
View examples
|
||||
</LinkButton>
|
||||
<LinkButton
|
||||
to={docsIntegrationPath(integration.identifier)}
|
||||
variant="secondary/small"
|
||||
@@ -83,13 +92,6 @@ export function ConnectToIntegrationSheet({
|
||||
variant="description"
|
||||
/>
|
||||
)}
|
||||
<RadioGroupItem
|
||||
id="custom"
|
||||
value="custom"
|
||||
label="Fetch/Existing SDK"
|
||||
description={`Alternatively, use ${integration.name} without our integration.`}
|
||||
variant="description"
|
||||
/>
|
||||
</RadioGroup>
|
||||
|
||||
{integrationMethod && (
|
||||
@@ -132,7 +134,5 @@ function SelectedIntegrationMethod({
|
||||
callbackUrl={callbackUrl}
|
||||
/>
|
||||
);
|
||||
case "custom":
|
||||
return <CustomHelp name={integration.name} />;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,8 @@ export function ConnectToOAuthForm({
|
||||
|
||||
const [form, { title, slug, scopes, hasCustomClient, customClientId, customClientSecret }] =
|
||||
useForm({
|
||||
lastSubmission: fetcher.data,
|
||||
// TODO: type this
|
||||
lastSubmission: fetcher.data as any,
|
||||
shouldRevalidate: "onSubmit",
|
||||
onValidate({ formData }) {
|
||||
return parse(formData, {
|
||||
|
||||
@@ -1,79 +1,124 @@
|
||||
import { CodeBlock } from "../code/CodeBlock";
|
||||
import { useState } from "react";
|
||||
import { CodeExample } from "~/routes/resources.codeexample";
|
||||
import { Api } from "~/services/externalApis/apis.server";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Feedback } from "../Feedback";
|
||||
import { Header1, Header2 } from "../primitives/Headers";
|
||||
import { Paragraph } from "../primitives/Paragraph";
|
||||
import { TextLink } from "../primitives/TextLink";
|
||||
|
||||
const fallbackExamples = [
|
||||
{
|
||||
title: "Post to Slack when meetings are booked or cancelled.",
|
||||
slug: "cal-slack-meeting-alert",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/cal-http-endpoint.ts",
|
||||
},
|
||||
{
|
||||
title: "Translate some text with DeepL.",
|
||||
slug: "translate-text-with-deepl",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/deepl.ts",
|
||||
},
|
||||
{
|
||||
title: "Create a Discord bot and send a message to a channel.",
|
||||
slug: "discord-bot-send-message",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/discord.ts",
|
||||
},
|
||||
{
|
||||
title: "Retrieve a Notion page by ID.",
|
||||
slug: "retrieve-notion-page",
|
||||
version: "1.0.0",
|
||||
codeUrl: "https://github.com/triggerdotdev/api-reference/raw/main/src/notion.ts",
|
||||
},
|
||||
];
|
||||
|
||||
export function CustomHelp({ api }: { api: Api }) {
|
||||
const [selectedExample, setSelectedExample] = useState(0);
|
||||
|
||||
const changeCodeExample = (index: number) => {
|
||||
setSelectedExample(index);
|
||||
};
|
||||
|
||||
export function CustomHelp({ name }: { name: string }) {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<Header1 className="mb-2">You can use any API with requests or an SDK</Header1>
|
||||
<Header2 className="mb-2">How to use an SDK</Header2>
|
||||
<Header1 className="mb-2">Using an API with an SDK or requests</Header1>
|
||||
<Paragraph spacing>
|
||||
You can call SDK methods from inside the run function, but you should wrap them in a Task to
|
||||
make sure they're resumable.
|
||||
You can use Trigger.dev with any existing Node SDK or even just using fetch. You can
|
||||
subscribe to any API with{" "}
|
||||
<TextLink href="https://trigger.dev/docs/documentation/concepts/http-endpoints">
|
||||
HTTP endpoints
|
||||
</TextLink>{" "}
|
||||
and perform actions by wrapping tasks using{" "}
|
||||
<TextLink
|
||||
href="https://trigger.dev/docs/documentation/guides/writing-jobs-step-by-step#create-your-own-tasks"
|
||||
className="font-mono"
|
||||
>
|
||||
io.runTask
|
||||
</TextLink>
|
||||
. This makes your background job resumable and appear in our dashboard.
|
||||
</Paragraph>
|
||||
<Paragraph spacing>Here's an example with the official GitHub SDK</Paragraph>
|
||||
<CodeBlock
|
||||
code={`
|
||||
client.defineJob({
|
||||
id: "scheduled-job-1",
|
||||
name: "Scheduled Job 1",
|
||||
version: "0.1.1",
|
||||
trigger: cronTrigger({
|
||||
cron: "*/5 * * * *", // every 5 minutes
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
//wrap an SDK call in io.runTask so it's resumable and displays in logs
|
||||
const repo = await io.runTask(
|
||||
"Get repo",
|
||||
//you can add metadata to the task to improve the display in the logs
|
||||
{ name: "Get repo", icon: "github" },
|
||||
async () => {
|
||||
//this is the regular GitHub SDK
|
||||
const response = await octokit.rest.repos.get({
|
||||
owner: "triggerdotdev",
|
||||
repo: "trigger.dev",
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
`}
|
||||
highlightedRanges={[[9, 22]]}
|
||||
className="mb-4"
|
||||
/>
|
||||
<Header2 className="mb-2">How to use fetch</Header2>
|
||||
<Paragraph spacing>
|
||||
You can use the fetch API to make requests to any API. Or a different request library like
|
||||
axios if you'd prefer. Again wrapping the request in a Task will make sure it's resumable.
|
||||
</Paragraph>
|
||||
<CodeBlock
|
||||
code={`
|
||||
client.defineJob({
|
||||
id: "scheduled-job-1",
|
||||
name: "Scheduled Job 1",
|
||||
version: "0.1.1",
|
||||
trigger: cronTrigger({
|
||||
cron: "*/5 * * * *", // every 5 minutes
|
||||
}),
|
||||
run: async (payload, io, ctx) => {
|
||||
//wrap anything in io.runTask so it's resumable and displays in logs
|
||||
const repo = await io.runTask(
|
||||
"Get org",
|
||||
//you can add metadata to the task to improve the display in the logs
|
||||
{ name: "Get org", icon: "github" },
|
||||
async () => {
|
||||
//you can use fetch, axios, or any other library to make requests
|
||||
const response = await fetch('https://api.github.com/orgs/nodejs');
|
||||
return response.json();
|
||||
}
|
||||
);
|
||||
},
|
||||
});
|
||||
`}
|
||||
highlightedRanges={[[9, 19]]}
|
||||
className="mb-4"
|
||||
/>
|
||||
|
||||
{api.examples && api.examples.length > 0 ? (
|
||||
<>
|
||||
<Header2 className="mb-2">Example {api.name} code</Header2>
|
||||
<Paragraph spacing className="mb-4">
|
||||
This is how you can use {api.name} with Trigger.dev. This code can be copied and
|
||||
modified to suit your use-case.
|
||||
</Paragraph>
|
||||
{api.examples.length > 1 && (
|
||||
<div className=" flex w-full flex-row gap-4 overflow-x-scroll scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700">
|
||||
{api.examples?.map((example, index) => (
|
||||
<button
|
||||
onClick={() => changeCodeExample(index)}
|
||||
key={example.codeUrl}
|
||||
className={cn(
|
||||
"w-64 min-w-[16rem] p-2 transition-colors duration-300 sm:w-full sm:rounded",
|
||||
"border-px focus:border-px cursor-pointer border border-slate-900 bg-slate-900 text-slate-300 transition duration-300 hover:bg-slate-800 focus:border focus:border-indigo-600"
|
||||
)}
|
||||
>
|
||||
{example.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<CodeExample example={api.examples[selectedExample]} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Header2 className="mb-2">Example code using fetch / an existing SDK</Header2>
|
||||
<Paragraph spacing className="mb-4">
|
||||
You can use one of our examples below as a starting point / reference for your projects.
|
||||
Please{" "}
|
||||
<Feedback
|
||||
button={
|
||||
<span className="cursor-pointer text-indigo-500 transition duration-300 hover:text-indigo-400">
|
||||
reach out to us
|
||||
</span>
|
||||
}
|
||||
defaultValue="help"
|
||||
/>{" "}
|
||||
if you're having any issues.
|
||||
</Paragraph>
|
||||
|
||||
<div className=" flex w-full flex-row gap-4 overflow-x-scroll scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700 sm:h-full">
|
||||
{fallbackExamples.map((example, index) => (
|
||||
<button
|
||||
onClick={() => changeCodeExample(index)}
|
||||
key={example.codeUrl}
|
||||
className={cn(
|
||||
"w-64 min-w-[16rem] p-2 transition-colors duration-300 sm:w-full sm:rounded",
|
||||
"border-px focus:border-px cursor-pointer border border-slate-900 bg-slate-900 text-slate-300 transition duration-300 hover:bg-slate-800 focus:border focus:border-indigo-600"
|
||||
)}
|
||||
>
|
||||
{example.title}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<CodeExample example={fallbackExamples[selectedExample]} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { useFetcher } from "@remix-run/react";
|
||||
import React from "react";
|
||||
import { Api } from "~/services/externalApis/apis";
|
||||
import { Button } from "../primitives/Buttons";
|
||||
import { Callout } from "../primitives/Callout";
|
||||
import { Api } from "~/services/externalApis/apis.server";
|
||||
import { Header1 } from "../primitives/Headers";
|
||||
import { NamedIconInBox } from "../primitives/NamedIcon";
|
||||
import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTrigger } from "../primitives/Sheet";
|
||||
import { CustomHelp } from "./CustomHelp";
|
||||
import { CheckIcon } from "@heroicons/react/24/solid";
|
||||
import { useFetcher } from "@remix-run/react";
|
||||
import { Paragraph } from "../primitives/Paragraph";
|
||||
|
||||
export function NoIntegrationSheet({
|
||||
api,
|
||||
@@ -31,27 +27,9 @@ export function NoIntegrationSheet({
|
||||
<NamedIconInBox name={api.identifier} className="h-9 w-9" />
|
||||
<Header1>{api.name}</Header1>
|
||||
</div>
|
||||
{requested ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<CheckIcon className="h-4 w-4 text-green-500" />
|
||||
<Paragraph variant="small">
|
||||
We'll let you know when the Integration is available.
|
||||
</Paragraph>
|
||||
</div>
|
||||
) : (
|
||||
<fetcher.Form method="post" action={`/resources/apivote/${api.identifier}`}>
|
||||
<Button
|
||||
variant="primary/small"
|
||||
disabled={isLoading}
|
||||
LeadingIcon={isLoading ? "spinner-white" : undefined}
|
||||
>
|
||||
{isLoading ? "Saving…" : `I want an Integration for ${api.name}`}
|
||||
</Button>
|
||||
</fetcher.Form>
|
||||
)}
|
||||
</SheetHeader>
|
||||
<SheetBody>
|
||||
<CustomHelp name={api.name} />
|
||||
<CustomHelp api={api} />
|
||||
</SheetBody>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ConnectToOAuthForm } from "./ConnectToOAuthForm";
|
||||
import { Paragraph } from "../primitives/Paragraph";
|
||||
import { Client } from "~/presenters/IntegrationsPresenter.server";
|
||||
import { UpdateOAuthForm } from "./UpdateOAuthForm";
|
||||
import { LinkButton } from "../primitives/Buttons";
|
||||
|
||||
export function SelectOAuthMethod({
|
||||
integration,
|
||||
@@ -76,7 +77,7 @@ export function SelectOAuthMethod({
|
||||
id="EXTERNAL"
|
||||
value="EXTERNAL"
|
||||
label="Your users"
|
||||
description="We will give you OAuth React components so you can connect as your users."
|
||||
description="Use an external authentication provider or your own user database to provide auth credentails of your users."
|
||||
variant="description"
|
||||
/>
|
||||
</RadioGroup>
|
||||
@@ -108,14 +109,19 @@ export function SelectOAuthMethod({
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
<Header2 className="mb-1 mt-4">User OAuth coming soon</Header2>
|
||||
<Header2 className="mb-1 mt-4">BYO Auth</Header2>
|
||||
<Paragraph spacing>
|
||||
End-user OAuth is going to be released soon. If you are interested in being an early
|
||||
beta tester then please{" "}
|
||||
<a href="mailto:founders@trigger.dev" className="text-indigo-500 underline">
|
||||
message us
|
||||
</a>
|
||||
.
|
||||
We support external authentication providers through Auth Resolvers. Read the docs to
|
||||
learn more:{" "}
|
||||
<LinkButton
|
||||
variant="secondary/small"
|
||||
LeadingIcon={"docs"}
|
||||
TrailingIcon={"external-link"}
|
||||
to="https://trigger.dev/docs/documentation/guides/using-integrations-byo-auth"
|
||||
target="_blank"
|
||||
>
|
||||
Bring your own Auth
|
||||
</LinkButton>
|
||||
</Paragraph>
|
||||
</>
|
||||
))}
|
||||
|
||||
@@ -45,7 +45,8 @@ export function UpdateOAuthForm({
|
||||
const { isManagedCloud } = useFeatures();
|
||||
|
||||
const [form, { title, scopes, hasCustomClient, customClientId, customClientSecret }] = useForm({
|
||||
lastSubmission: fetcher.data,
|
||||
// TODO: type this
|
||||
lastSubmission: fetcher.data as any,
|
||||
onValidate({ formData }) {
|
||||
return parse(formData, {
|
||||
schema,
|
||||
|
||||
@@ -40,7 +40,7 @@ export function DeleteJobDialogContent({
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col items-center gap-y-6">
|
||||
<div className="flex flex-col items-center justify-center gap-y-2">
|
||||
<div className="mt-5 flex flex-col items-center justify-center gap-y-2">
|
||||
<Header1>{title}</Header1>
|
||||
<Paragraph variant="small">ID: {slug}</Paragraph>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ProjectJob } from "~/hooks/useJobs";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { JobRunStatus } from "~/models/job.server";
|
||||
import { ProjectJob } from "~/presenters/JobListPresenter.server";
|
||||
import { jobPath, jobTestPath } from "~/utils/pathBuilder";
|
||||
import { Button } from "../primitives/Buttons";
|
||||
import { DateTime } from "../primitives/DateTime";
|
||||
@@ -27,10 +27,9 @@ import { JobStatusBadge } from "./JobStatusBadge";
|
||||
|
||||
export function JobsTable({ jobs, noResultsText }: { jobs: ProjectJob[]; noResultsText: string }) {
|
||||
const organization = useOrganization();
|
||||
const project = useProject();
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<Table containerClassName="mb-4">
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Job</TableHeaderCell>
|
||||
@@ -45,7 +44,7 @@ export function JobsTable({ jobs, noResultsText }: { jobs: ProjectJob[]; noResul
|
||||
<TableBody>
|
||||
{jobs.length > 0 ? (
|
||||
jobs.map((job) => {
|
||||
const path = jobPath(organization, project, job);
|
||||
const path = jobPath(organization, { slug: job.projectSlug }, job);
|
||||
return (
|
||||
<TableRow key={job.id} className="group">
|
||||
<TableCell to={path}>
|
||||
@@ -153,26 +152,25 @@ export function JobsTable({ jobs, noResultsText }: { jobs: ProjectJob[]; noResul
|
||||
<TableCellMenu isSticky>
|
||||
<PopoverMenuItem to={path} title="View Job" icon="eye" />
|
||||
<PopoverMenuItem
|
||||
to={jobTestPath(organization, project, job)}
|
||||
to={jobTestPath(organization, { slug: job.projectSlug }, job)}
|
||||
title="Test Job"
|
||||
icon="beaker"
|
||||
/>
|
||||
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="menu-item" LeadingIcon="trash-can">
|
||||
<Button variant="small-menu-item" LeadingIcon="trash-can" className="text-xs">
|
||||
Delete Job
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DeleteJobDialogContent
|
||||
id={job.id}
|
||||
title={job.title}
|
||||
slug={job.slug}
|
||||
environments={job.environments}
|
||||
/>
|
||||
</DialogHeader>
|
||||
<DialogHeader>Delete Job</DialogHeader>
|
||||
<DeleteJobDialogContent
|
||||
id={job.id}
|
||||
title={job.title}
|
||||
slug={job.slug}
|
||||
environments={job.environments}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</TableCellMenu>
|
||||
@@ -197,6 +195,8 @@ function classForJobStatus(status: JobRunStatus) {
|
||||
case "TIMED_OUT":
|
||||
case "WAITING_ON_CONNECTIONS":
|
||||
case "PENDING":
|
||||
case "UNRESOLVED_AUTH":
|
||||
case "INVALID_PAYLOAD":
|
||||
return "text-rose-500";
|
||||
default:
|
||||
return "";
|
||||
|
||||
@@ -11,7 +11,7 @@ export function AppContainer({
|
||||
}) {
|
||||
return (
|
||||
<BackgroundGradient showBackgroundGradient={showBackgroundGradient}>
|
||||
<div className={cn("grid h-full w-full grid-rows-[2.75rem_auto]")}>{children}</div>
|
||||
<div className={cn("grid h-full w-full grid-rows-1 overflow-hidden")}>{children}</div>
|
||||
</BackgroundGradient>
|
||||
);
|
||||
}
|
||||
@@ -40,8 +40,18 @@ export function BackgroundGradient({
|
||||
}
|
||||
|
||||
/** This container should be placed around the content on a page */
|
||||
export function PageContainer({ children }: { children: React.ReactNode }) {
|
||||
return <div className={cn("grid h-full grid-rows-[auto_1fr]")}>{children}</div>;
|
||||
export function PageContainer({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("grid h-full grid-rows-[auto_1fr] overflow-hidden", className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PageBody({
|
||||
@@ -52,7 +62,15 @@ export function PageBody({
|
||||
scrollable?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className={cn(scrollable ? "overflow-y-auto p-4 " : "overflow-hidden")}>{children}</div>
|
||||
<div
|
||||
className={cn(
|
||||
scrollable
|
||||
? "overflow-y-auto p-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700"
|
||||
: "overflow-hidden"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -60,10 +78,16 @@ export function PageBodyPadding({ children }: { children: React.ReactNode }) {
|
||||
return <div className="p-4">{children}</div>;
|
||||
}
|
||||
|
||||
export function MainCenteredContainer({ children }: { children: React.ReactNode }) {
|
||||
export function MainCenteredContainer({
|
||||
children,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="h-full w-full">
|
||||
<div className="mx-auto mt-[30vh] max-w-xs">{children}</div>
|
||||
<div className="h-full w-full overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700">
|
||||
<div className={cn("mx-auto mt-[25vh] max-w-xs overflow-y-auto", className)}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,26 +1,35 @@
|
||||
import { RouteMatch, useMatches } from "@remix-run/react";
|
||||
import { UIMatch, useMatches } from "@remix-run/react";
|
||||
import { Fragment, ReactNode } from "react";
|
||||
import { BreadcrumbIcon } from "../primitives/BreadcrumbIcon";
|
||||
import { Handle } from "~/utils/handle";
|
||||
import { LinkButton } from "../primitives/Buttons";
|
||||
|
||||
export type BreadcrumbItem = (match: RouteMatch, allMatches: RouteMatch[]) => ReactNode;
|
||||
export type BreadcrumbItem = (match: UIMatch, allMatches: UIMatch[]) => ReactNode;
|
||||
|
||||
export function Breadcrumb() {
|
||||
const matches = useMatches();
|
||||
const matches = useMatches() as UIMatch<unknown, Handle>[];
|
||||
|
||||
return (
|
||||
<div className="hidden items-center md:flex">
|
||||
{matches.map((match) => {
|
||||
if (!match.handle || !match.handle.breadcrumb) return null;
|
||||
<div className="flex items-center px-1">
|
||||
{matches
|
||||
.filter((b) => b.handle && b.handle.breadcrumb)
|
||||
.map((match, index) => {
|
||||
const breadcrumb = match.handle.breadcrumb as BreadcrumbItem;
|
||||
|
||||
const breadcrumb = match.handle.breadcrumb as BreadcrumbItem;
|
||||
|
||||
return (
|
||||
<Fragment key={match.id}>
|
||||
<BreadcrumbIcon />
|
||||
{breadcrumb(match, matches)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
return (
|
||||
<Fragment key={match.id}>
|
||||
{index !== 0 && <BreadcrumbIcon />} {breadcrumb(match, matches)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BreadcrumbLink({ title, to }: { title: string; to: string }) {
|
||||
return (
|
||||
<LinkButton to={to} variant="tertiary/small">
|
||||
{title}
|
||||
</LinkButton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import { Link, RouteMatch } from "@remix-run/react";
|
||||
import { useState } from "react";
|
||||
import { useJob } from "~/hooks/useJob";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { useJobs } from "~/hooks/useJobs";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { jobPath } from "~/utils/pathBuilder";
|
||||
import { LabelValueStack } from "../primitives/LabelValueStack";
|
||||
import { NamedIcon } from "../primitives/NamedIcon";
|
||||
import {
|
||||
Popover,
|
||||
PopoverArrowTrigger,
|
||||
PopoverContent,
|
||||
PopoverSectionHeader,
|
||||
} from "../primitives/Popover";
|
||||
|
||||
export function JobsMenu({ matches }: { matches: RouteMatch[] }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const organization = useOrganization(matches);
|
||||
const project = useProject(matches);
|
||||
const projectJobs = useJobs(matches);
|
||||
const currentJob = useJob(matches);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popover onOpenChange={(open) => setIsOpen(open)}>
|
||||
<PopoverArrowTrigger isOpen={isOpen}>
|
||||
{currentJob?.title ?? "Select a job"}
|
||||
</PopoverArrowTrigger>
|
||||
<PopoverContent
|
||||
className="min-w-[20rem] overflow-y-auto p-0 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700"
|
||||
align="start"
|
||||
collisionPadding={20}
|
||||
>
|
||||
<PopoverSectionHeader title="Jobs" />
|
||||
<div className="flex flex-col gap-1 p-1">
|
||||
{projectJobs.map((job) => {
|
||||
const isSelected = job.id === currentJob?.id;
|
||||
return (
|
||||
<Link
|
||||
key={job.id}
|
||||
to={jobPath(organization, project, job)}
|
||||
className={cn(
|
||||
"flex w-full items-center gap-2 rounded-md p-2 transition hover:bg-slate-800",
|
||||
isSelected && "bg-slate-750 group-hover:bg-slate-750"
|
||||
)}
|
||||
>
|
||||
<NamedIcon name={job.event.icon} className="h-8 w-8" />
|
||||
<LabelValueStack
|
||||
label={job.title}
|
||||
value={job.slug}
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
/>
|
||||
{isSelected && <NamedIcon name="check" className="mr-1 h-6 w-6" />}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { BookOpenIcon, ChatBubbleLeftRightIcon } from "@heroicons/react/20/solid";
|
||||
import { Link } from "@remix-run/react";
|
||||
import { Fragment } from "react";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Feedback } from "../Feedback";
|
||||
import { LogoIcon } from "../LogoIcon";
|
||||
import { Button, LinkButton } from "../primitives/Buttons";
|
||||
import { Breadcrumb } from "./Breadcrumb";
|
||||
import { docsRoot } from "~/utils/pathBuilder";
|
||||
|
||||
export function NavBar() {
|
||||
return (
|
||||
<div className="z-50 flex w-full items-center justify-between gap-2 border-b border-uiBorder py-1 pl-1 pr-2.5">
|
||||
<div className="flex gap-0.5">
|
||||
<Link to="/" className="p-2">
|
||||
<LogoIcon className="h-5 w-5" />
|
||||
</Link>
|
||||
<Breadcrumb />
|
||||
<MobileDropdownMenu />
|
||||
</div>
|
||||
<div className="hidden items-center gap-2 sm:flex">
|
||||
<LinkButton to={docsRoot()} variant="secondary/small" LeadingIcon={BookOpenIcon}>
|
||||
Documentation
|
||||
</LinkButton>
|
||||
<Feedback
|
||||
button={
|
||||
<Button
|
||||
variant="secondary/small"
|
||||
LeadingIcon={ChatBubbleLeftRightIcon}
|
||||
shortcut={{ key: "f" }}
|
||||
>
|
||||
Help & feedback
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BreadcrumbLink({ title, to }: { title: string; to: string }) {
|
||||
return (
|
||||
<LinkButton to={to} variant="tertiary/small">
|
||||
{title}
|
||||
</LinkButton>
|
||||
);
|
||||
}
|
||||
|
||||
function MobileDropdownMenu() {
|
||||
return (
|
||||
<Popover className="block sm:hidden">
|
||||
<Popover.Button
|
||||
className="bg-slate-70 relative z-10 flex h-8 w-8 items-center justify-center rounded border-none bg-opacity-50 focus-visible:border-none focus-visible:outline-none"
|
||||
aria-label="Toggle Navigation"
|
||||
>
|
||||
{({ open }) => <MobileNavIcon open={open} />}
|
||||
</Popover.Button>
|
||||
<Transition.Root>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="duration-150 ease-out"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="duration-150 ease-in"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Popover.Overlay className="absolute left-0 top-0 h-full w-full origin-top bg-midnight-900/80" />
|
||||
</Transition.Child>
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="duration-150 ease-out"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="duration-100 ease-in"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
>
|
||||
<Popover.Panel
|
||||
as="div"
|
||||
className="absolute inset-x-6 top-0 mt-20 flex origin-top flex-col gap-4 rounded-md bg-slate-800 p-4 text-lg tracking-tight text-slate-900 shadow-xl ring-1 ring-slate-700"
|
||||
>
|
||||
{/* <PrimaryA
|
||||
href={docsRoot()}
|
||||
target="_blank"
|
||||
className="max-w-full"
|
||||
>
|
||||
<ArrowTopRightOnSquareIcon className="-ml-1 h-4 w-4" />
|
||||
Documentation
|
||||
</PrimaryA>
|
||||
<PrimaryButton
|
||||
data-attr="posthog-feedback-button"
|
||||
className="max-w-full"
|
||||
>
|
||||
<ChatBubbleLeftRightIcon className="-ml-1 h-4 w-4" />
|
||||
Send us feedback
|
||||
</PrimaryButton>
|
||||
<SecondaryLink to="/logout" className="max-w-full">
|
||||
Logout
|
||||
</SecondaryLink> */}
|
||||
</Popover.Panel>
|
||||
</Transition.Child>
|
||||
</Transition.Root>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
function MobileNavIcon({ open }: { open: boolean }) {
|
||||
return (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="h-3.5 w-3.5 overflow-visible stroke-slate-300"
|
||||
fill="none"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
>
|
||||
<path
|
||||
d="M0 1H14M0 7H14M0 13H14"
|
||||
className={cn("origin-center transition", open && "scale-90 opacity-0")}
|
||||
/>
|
||||
<path
|
||||
d="M2 2L12 12M12 2L2 12"
|
||||
className={cn("origin-center transition", !open && "scale-90 opacity-0")}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { useNavigation } from "@remix-run/react";
|
||||
import { Spinner } from "../primitives/Spinner";
|
||||
|
||||
export function PageNavigationIndicator() {
|
||||
const navigation = useNavigation();
|
||||
if (navigation.state === "loading") {
|
||||
return <Spinner color="muted" className="h-4 w-4" />;
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
import { useMatches } from "@remix-run/react";
|
||||
import { motion } from "framer-motion";
|
||||
import { useOptionalJob } from "~/hooks/useJob";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Handle } from "~/utils/handle";
|
||||
import {
|
||||
accountPath,
|
||||
organizationBillingPath,
|
||||
organizationTeamPath,
|
||||
projectEnvironmentsPath,
|
||||
projectIntegrationsPath,
|
||||
projectPath,
|
||||
projectTriggersPath,
|
||||
} from "~/utils/pathBuilder";
|
||||
import { UserProfilePhoto } from "../UserProfilePhoto";
|
||||
import { NavLinkButton } from "../primitives/Buttons";
|
||||
import { NamedIcon, type IconNames } from "../primitives/NamedIcon";
|
||||
import { SimpleTooltip } from "../primitives/Tooltip";
|
||||
|
||||
export function SideMenuContainer({ children }: { children: React.ReactNode }) {
|
||||
return <div className="flex h-full w-full justify-stretch overflow-hidden">{children}</div>;
|
||||
}
|
||||
|
||||
const expandedWith = "14rem";
|
||||
const collapsedWith = "2.81rem";
|
||||
|
||||
const menuVariants = {
|
||||
expanded: {
|
||||
minWidth: expandedWith,
|
||||
width: expandedWith,
|
||||
},
|
||||
collapsed: {
|
||||
minWidth: collapsedWith,
|
||||
width: collapsedWith,
|
||||
},
|
||||
};
|
||||
|
||||
export function ProjectSideMenu() {
|
||||
const organization = useOrganization();
|
||||
const project = useProject();
|
||||
const matches = useMatches();
|
||||
|
||||
//the deepest route `handle` determines if the menu is expanded
|
||||
const deepestMatch = matches.at(-1);
|
||||
const handle = deepestMatch?.handle as Handle;
|
||||
const isCollapsed = handle?.expandSidebar ? !handle.expandSidebar : true;
|
||||
|
||||
const job = useOptionalJob();
|
||||
const jobsActive =
|
||||
job !== undefined ||
|
||||
deepestMatch?.id === "routes/_app.orgs.$organizationSlug.projects.$projectParam._index";
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
animate={isCollapsed ? "collapsed" : "expanded"}
|
||||
variants={menuVariants}
|
||||
initial={isCollapsed ? "collapsed" : "expanded"}
|
||||
className={cn(
|
||||
"flex h-full flex-col justify-between overflow-hidden border-r border-uiBorder p-1 transition duration-300 ease-in-out"
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
<SideMenuItem
|
||||
name="Jobs"
|
||||
icon="job"
|
||||
to={projectPath(organization, project)}
|
||||
isCollapsed={isCollapsed}
|
||||
forceActive={jobsActive}
|
||||
data-action="jobs"
|
||||
/>
|
||||
<SideMenuItem
|
||||
name="Integrations"
|
||||
icon="integration"
|
||||
to={projectIntegrationsPath(organization, project)}
|
||||
isCollapsed={isCollapsed}
|
||||
hasWarning={project.hasUnconfiguredIntegrations}
|
||||
data-action="integrations"
|
||||
/>
|
||||
<SideMenuItem
|
||||
name="Triggers"
|
||||
icon="trigger"
|
||||
to={projectTriggersPath(organization, project)}
|
||||
isCollapsed={isCollapsed}
|
||||
hasWarning={project.hasInactiveExternalTriggers}
|
||||
data-action="triggers"
|
||||
/>
|
||||
<SideMenuItem
|
||||
name="Environments & API Keys"
|
||||
icon="environment"
|
||||
to={projectEnvironmentsPath(organization, project)}
|
||||
isCollapsed={isCollapsed}
|
||||
data-action="environments & api keys"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<SideMenuItem
|
||||
name="Team"
|
||||
icon="team"
|
||||
to={organizationTeamPath(organization)}
|
||||
isCollapsed={isCollapsed}
|
||||
data-action="team"
|
||||
/>
|
||||
<SideMenuItem
|
||||
name="Usage & Billing"
|
||||
icon="billing"
|
||||
to={organizationBillingPath(organization)}
|
||||
isCollapsed={isCollapsed}
|
||||
data-action="usage & billing"
|
||||
/>
|
||||
<SideMenuItem
|
||||
name="Account"
|
||||
icon={UserProfilePhoto}
|
||||
to={accountPath()}
|
||||
isCollapsed={isCollapsed}
|
||||
data-action="account"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
const itemVariants = {
|
||||
expanded: {
|
||||
opacity: 1,
|
||||
},
|
||||
collapsed: {
|
||||
opacity: 0,
|
||||
},
|
||||
};
|
||||
|
||||
function SideMenuItem({
|
||||
icon,
|
||||
name,
|
||||
to,
|
||||
isCollapsed,
|
||||
forceActive,
|
||||
hasWarning = false,
|
||||
}: {
|
||||
icon: IconNames | React.ComponentType<any>;
|
||||
name: string;
|
||||
to: string;
|
||||
isCollapsed: boolean;
|
||||
hasWarning?: boolean;
|
||||
forceActive?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<SimpleTooltip
|
||||
button={
|
||||
<NavLinkButton
|
||||
variant="menu-item"
|
||||
fullWidth
|
||||
textAlignLeft
|
||||
LeadingIcon={icon}
|
||||
leadingIconClassName="text-dimmed"
|
||||
to={to}
|
||||
className={({ isActive, isPending }) => {
|
||||
if (forceActive !== undefined) {
|
||||
isActive = forceActive;
|
||||
}
|
||||
return cn(
|
||||
"relative",
|
||||
isActive
|
||||
? "bg-slate-800 text-bright group-hover:bg-slate-800"
|
||||
: "text-dimmed group-hover:bg-slate-850 group-hover:text-bright"
|
||||
);
|
||||
}}
|
||||
>
|
||||
<motion.span
|
||||
className="shrink-0 pl-1"
|
||||
animate={isCollapsed ? "collapsed" : "expanded"}
|
||||
variants={itemVariants}
|
||||
initial={isCollapsed ? "collapsed" : "expanded"}
|
||||
>
|
||||
{name}
|
||||
</motion.span>
|
||||
{hasWarning && <NamedIcon name="error" className="absolute left-1 top-1 h-4 w-4" />}
|
||||
</NavLinkButton>
|
||||
}
|
||||
content={name}
|
||||
side="right"
|
||||
hidden={!isCollapsed}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { RouteMatch } from "@remix-run/react";
|
||||
import { Fragment, useState } from "react";
|
||||
import simplur from "simplur";
|
||||
import { Badge } from "~/components/primitives/Badge";
|
||||
import { useIsNewOrganizationPage, useOrganizations } from "~/hooks/useOrganizations";
|
||||
import { useOptionalProject } from "~/hooks/useProject";
|
||||
import { newOrganizationPath, newProjectPath, projectPath } from "~/utils/pathBuilder";
|
||||
import {
|
||||
Popover,
|
||||
PopoverArrowTrigger,
|
||||
PopoverContent,
|
||||
PopoverMenuItem,
|
||||
PopoverSectionHeader,
|
||||
} from "../primitives/Popover";
|
||||
|
||||
export function ProjectsMenu({ matches }: { matches: RouteMatch[] }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const organizations = useOrganizations(matches);
|
||||
const isNewOrgPage = useIsNewOrganizationPage(matches);
|
||||
const currentProject = useOptionalProject(matches);
|
||||
|
||||
if (isNewOrgPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popover onOpenChange={(open) => setIsOpen(open)}>
|
||||
<PopoverArrowTrigger isOpen={isOpen}>
|
||||
{currentProject?.name ?? "Select a project"}
|
||||
</PopoverArrowTrigger>
|
||||
<PopoverContent
|
||||
className="min-w-[20rem] overflow-y-auto p-0 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700"
|
||||
align="start"
|
||||
collisionPadding={20}
|
||||
>
|
||||
{organizations.map((organization) => (
|
||||
<Fragment key={organization.id}>
|
||||
<PopoverSectionHeader title={organization.title} />
|
||||
<div className="flex flex-col gap-1 p-1 ">
|
||||
{organization.projects.map((project) => {
|
||||
const isSelected = project.id === currentProject?.id;
|
||||
return (
|
||||
<PopoverMenuItem
|
||||
key={project.id}
|
||||
to={projectPath(organization, project)}
|
||||
title={
|
||||
<div className="flex w-full items-center justify-between pl-1 text-bright">
|
||||
<span className="grow truncate text-left">{project.name}</span>
|
||||
<Badge className="mr-0.5">{simplur`${project._count.jobs} job[|s]`}</Badge>
|
||||
</div>
|
||||
}
|
||||
isSelected={isSelected}
|
||||
icon="folder"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<PopoverMenuItem
|
||||
to={newProjectPath(organization)}
|
||||
title="New Project"
|
||||
isSelected={false}
|
||||
icon="plus"
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
<div className="border-t border-slate-700 p-1">
|
||||
<PopoverMenuItem
|
||||
to={newOrganizationPath()}
|
||||
title="New Organization"
|
||||
isSelected={false}
|
||||
icon="plus"
|
||||
/>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
import {
|
||||
AcademicCapIcon,
|
||||
ArrowRightOnRectangleIcon,
|
||||
ChartBarIcon,
|
||||
EllipsisHorizontalIcon,
|
||||
} from "@heroicons/react/20/solid";
|
||||
import { UserGroupIcon, UserPlusIcon } from "@heroicons/react/24/solid";
|
||||
import { useNavigation } from "@remix-run/react";
|
||||
import { IconExclamationCircle } from "@tabler/icons-react";
|
||||
import { AnchorHTMLAttributes, Fragment, useEffect, useRef, useState } from "react";
|
||||
import { MatchedOrganization } from "~/hooks/useOrganizations";
|
||||
import { usePathName } from "~/hooks/usePathName";
|
||||
import { MatchedProject } from "~/hooks/useProject";
|
||||
import { User } from "~/models/user.server";
|
||||
import { cn } from "~/utils/cn";
|
||||
import {
|
||||
accountPath,
|
||||
inviteTeamMemberPath,
|
||||
logoutPath,
|
||||
newOrganizationPath,
|
||||
newProjectPath,
|
||||
organizationBillingPath,
|
||||
organizationIntegrationsPath,
|
||||
organizationPath,
|
||||
organizationTeamPath,
|
||||
projectEnvironmentsPath,
|
||||
projectHttpEndpointsPath,
|
||||
projectPath,
|
||||
projectSetupPath,
|
||||
projectTriggersPath,
|
||||
} from "~/utils/pathBuilder";
|
||||
import { Feedback } from "../Feedback";
|
||||
import { ImpersonationBanner } from "../ImpersonationBanner";
|
||||
import { LogoIcon } from "../LogoIcon";
|
||||
import { UserAvatar, UserProfilePhoto } from "../UserProfilePhoto";
|
||||
import { Button, LinkButton } from "../primitives/Buttons";
|
||||
import { Icon } from "../primitives/Icon";
|
||||
import { type IconNames } from "../primitives/NamedIcon";
|
||||
import { Paragraph } from "../primitives/Paragraph";
|
||||
import {
|
||||
Popover,
|
||||
PopoverArrowTrigger,
|
||||
PopoverContent,
|
||||
PopoverCustomTrigger,
|
||||
PopoverMenuItem,
|
||||
PopoverSectionHeader,
|
||||
} from "../primitives/Popover";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip";
|
||||
import { useFeatures } from "~/hooks/useFeatures";
|
||||
|
||||
type SideMenuUser = Pick<User, "email" | "admin"> & { isImpersonating: boolean };
|
||||
type SideMenuProject = Pick<
|
||||
MatchedProject,
|
||||
"id" | "name" | "slug" | "hasInactiveExternalTriggers" | "jobCount" | "httpEndpointCount"
|
||||
>;
|
||||
|
||||
type SideMenuProps = {
|
||||
user: SideMenuUser;
|
||||
project: SideMenuProject;
|
||||
organization: MatchedOrganization;
|
||||
organizations: MatchedOrganization[];
|
||||
};
|
||||
|
||||
export function SideMenu({ user, project, organization, organizations }: SideMenuProps) {
|
||||
const borderRef = useRef<HTMLDivElement>(null);
|
||||
const [showHeaderDivider, setShowHeaderDivider] = useState(false);
|
||||
const { isManagedCloud } = useFeatures();
|
||||
|
||||
useEffect(() => {
|
||||
const handleScroll = () => {
|
||||
if (borderRef.current) {
|
||||
const shouldShowHeaderDivider = borderRef.current.scrollTop > 1;
|
||||
if (showHeaderDivider !== shouldShowHeaderDivider) {
|
||||
setShowHeaderDivider(shouldShowHeaderDivider);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
borderRef.current?.addEventListener("scroll", handleScroll);
|
||||
return () => borderRef.current?.removeEventListener("scroll", handleScroll);
|
||||
}, [showHeaderDivider]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full flex-col gap-y-8 overflow-hidden border-r border-ui-border transition"
|
||||
)}
|
||||
>
|
||||
<div className="flex h-full flex-col">
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-between border-b bg-background px-1 py-1 transition",
|
||||
showHeaderDivider ? " border-border" : "border-transparent"
|
||||
)}
|
||||
>
|
||||
<ProjectSelector
|
||||
organization={organization}
|
||||
organizations={organizations}
|
||||
project={project}
|
||||
/>
|
||||
<UserMenu user={user} />
|
||||
</div>
|
||||
<div
|
||||
className="h-full overflow-hidden overflow-y-auto pt-2 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700"
|
||||
ref={borderRef}
|
||||
>
|
||||
<div className="mb-6 flex flex-col gap-1 px-1">
|
||||
<SideMenuHeader title={project.name || "No project found"}>
|
||||
<PopoverMenuItem
|
||||
to={projectSetupPath(organization, project)}
|
||||
title="Framework setup"
|
||||
icon="plus"
|
||||
/>
|
||||
</SideMenuHeader>
|
||||
<SideMenuItem
|
||||
name="Jobs"
|
||||
icon="job"
|
||||
iconColor="text-indigo-500"
|
||||
count={project.jobCount}
|
||||
to={projectPath(organization, project)}
|
||||
data-action="jobs"
|
||||
/>
|
||||
<SideMenuItem
|
||||
name="Triggers"
|
||||
icon="trigger"
|
||||
iconColor="text-amber-500"
|
||||
to={projectTriggersPath(organization, project)}
|
||||
data-action="triggers"
|
||||
hasWarning={project.hasInactiveExternalTriggers}
|
||||
/>
|
||||
<SideMenuItem
|
||||
name="HTTP endpoints"
|
||||
icon="http-endpoint"
|
||||
iconColor="text-green-500"
|
||||
count={project.httpEndpointCount}
|
||||
to={projectHttpEndpointsPath(organization, project)}
|
||||
data-action="httpendpoints"
|
||||
/>
|
||||
<SideMenuItem
|
||||
name="Environments & API Keys"
|
||||
icon="environment"
|
||||
iconColor="text-rose-500"
|
||||
to={projectEnvironmentsPath(organization, project)}
|
||||
data-action="environments & api keys"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-1 flex flex-col gap-1 px-1">
|
||||
<SideMenuHeader title={organization.title}>
|
||||
<PopoverMenuItem to={newProjectPath(organization)} title="New Project" icon="plus" />
|
||||
<PopoverMenuItem
|
||||
to={inviteTeamMemberPath(organization)}
|
||||
title="Invite team member"
|
||||
icon={UserPlusIcon}
|
||||
leadingIconClassName="text-indigo-500"
|
||||
/>
|
||||
</SideMenuHeader>
|
||||
<SideMenuItem
|
||||
name="Integrations"
|
||||
icon="integration"
|
||||
to={organizationIntegrationsPath(organization)}
|
||||
data-action="integrations"
|
||||
hasWarning={organization.hasUnconfiguredIntegrations}
|
||||
/>
|
||||
<SideMenuItem
|
||||
name="Projects"
|
||||
icon="folder"
|
||||
to={organizationPath(organization)}
|
||||
data-action="projects"
|
||||
/>
|
||||
<SideMenuItem
|
||||
name="Team"
|
||||
icon={UserGroupIcon}
|
||||
to={organizationTeamPath(organization)}
|
||||
iconColor="text-sky-500"
|
||||
data-action="team"
|
||||
/>
|
||||
<SideMenuItem
|
||||
name={isManagedCloud ? "Usage & Billing" : "Usage"}
|
||||
icon={ChartBarIcon}
|
||||
to={organizationBillingPath(organization)}
|
||||
iconColor="text-pink-500"
|
||||
data-action="usage & billing"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1 border-t border-border p-1">
|
||||
<SideMenuItem
|
||||
name="Documentation"
|
||||
icon="docs"
|
||||
to="https://trigger.dev/docs"
|
||||
data-action="documentation"
|
||||
target="_blank"
|
||||
/>
|
||||
<SideMenuItem
|
||||
name="Changelog"
|
||||
icon="star"
|
||||
to="https://trigger.dev/changelog"
|
||||
data-action="changelog"
|
||||
target="_blank"
|
||||
/>
|
||||
|
||||
<Feedback
|
||||
button={
|
||||
<Button
|
||||
variant="small-menu-item"
|
||||
LeadingIcon="log"
|
||||
data-action="help & feedback"
|
||||
fullWidth
|
||||
textAlignLeft
|
||||
>
|
||||
Help & Feedback
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ProjectSelector({
|
||||
project,
|
||||
organization,
|
||||
organizations,
|
||||
}: {
|
||||
project: SideMenuProject;
|
||||
organization: MatchedOrganization;
|
||||
organizations: MatchedOrganization[];
|
||||
}) {
|
||||
const [isOrgMenuOpen, setOrgMenuOpen] = useState(false);
|
||||
const navigation = useNavigation();
|
||||
|
||||
useEffect(() => {
|
||||
setOrgMenuOpen(false);
|
||||
}, [navigation.location?.pathname]);
|
||||
|
||||
return (
|
||||
<Popover onOpenChange={(open) => setOrgMenuOpen(open)} open={isOrgMenuOpen}>
|
||||
<PopoverArrowTrigger
|
||||
isOpen={isOrgMenuOpen}
|
||||
overflowHidden
|
||||
className="h-7 w-full justify-between overflow-hidden py-1 pl-2"
|
||||
>
|
||||
<LogoIcon className="relative -top-px mr-2 h-4 w-4 min-w-[1rem]" />
|
||||
<span className="truncate">{organization.title ?? "Select an organization"}</span>
|
||||
</PopoverArrowTrigger>
|
||||
<PopoverContent
|
||||
className="min-w-[16rem] overflow-y-auto p-0 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700"
|
||||
align="start"
|
||||
style={{ maxHeight: `calc(var(--radix-popover-content-available-height) - 10vh)` }}
|
||||
>
|
||||
{organizations.map((organization) => (
|
||||
<Fragment key={organization.id}>
|
||||
<PopoverSectionHeader title={organization.title} />
|
||||
<div className="flex flex-col gap-1 p-1">
|
||||
{organization.projects.map((p) => {
|
||||
const isSelected = p.id === project.id;
|
||||
return (
|
||||
<PopoverMenuItem
|
||||
key={p.id}
|
||||
to={projectPath(organization, p)}
|
||||
title={
|
||||
<div className="flex w-full items-center justify-between text-bright">
|
||||
<span className="grow truncate text-left">{p.name}</span>
|
||||
<MenuCount count={p.jobCount} />
|
||||
</div>
|
||||
}
|
||||
isSelected={isSelected}
|
||||
icon="folder"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Fragment>
|
||||
))}
|
||||
<div className="border-t border-slate-800 p-1">
|
||||
<PopoverMenuItem to={newOrganizationPath()} title="New Organization" icon="plus" />
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
function UserMenu({ user }: { user: SideMenuUser }) {
|
||||
const [isProfileMenuOpen, setProfileMenuOpen] = useState(false);
|
||||
const navigation = useNavigation();
|
||||
|
||||
useEffect(() => {
|
||||
setProfileMenuOpen(false);
|
||||
}, [navigation.location?.pathname]);
|
||||
|
||||
return (
|
||||
<Popover onOpenChange={(open) => setProfileMenuOpen(open)}>
|
||||
<PopoverCustomTrigger isOpen={isProfileMenuOpen} className="p-1">
|
||||
<UserProfilePhoto
|
||||
className={cn(
|
||||
"h-5 w-5 text-slate-600",
|
||||
user.isImpersonating && "rounded-full border border-yellow-500"
|
||||
)}
|
||||
/>
|
||||
</PopoverCustomTrigger>
|
||||
<PopoverContent
|
||||
className="min-w-[12rem] overflow-y-auto p-0 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700"
|
||||
align="start"
|
||||
>
|
||||
<Fragment>
|
||||
<PopoverSectionHeader title={user.email} variant="extra-small" />
|
||||
<div className="flex flex-col gap-1 p-1">
|
||||
{user.isImpersonating && <ImpersonationBanner />}
|
||||
{user.admin && (
|
||||
<PopoverMenuItem
|
||||
to={"/admin"}
|
||||
title="Admin"
|
||||
icon={AcademicCapIcon}
|
||||
leadingIconClassName="text-yellow-500"
|
||||
/>
|
||||
)}
|
||||
<PopoverMenuItem
|
||||
to={accountPath()}
|
||||
title="View profile"
|
||||
icon={UserProfilePhoto}
|
||||
leadingIconClassName="text-indigo-500"
|
||||
/>
|
||||
<PopoverMenuItem
|
||||
to={logoutPath()}
|
||||
title="Log out"
|
||||
icon={ArrowRightOnRectangleIcon}
|
||||
leadingIconClassName="text-rose-500"
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
function SideMenuHeader({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
const [isHeaderMenuOpen, setHeaderMenuOpen] = useState(false);
|
||||
const navigation = useNavigation();
|
||||
|
||||
useEffect(() => {
|
||||
setHeaderMenuOpen(false);
|
||||
}, [navigation.location?.pathname]);
|
||||
|
||||
return (
|
||||
<div className="group flex items-center justify-between pl-1.5">
|
||||
<Paragraph
|
||||
variant="extra-extra-small/caps"
|
||||
className="cursor-default truncate text-slate-500"
|
||||
>
|
||||
{title}
|
||||
</Paragraph>
|
||||
<Popover onOpenChange={(open) => setHeaderMenuOpen(open)} open={isHeaderMenuOpen}>
|
||||
<PopoverCustomTrigger className="p-1">
|
||||
<EllipsisHorizontalIcon className="h-4 w-4 text-slate-500 transition group-hover:text-bright" />
|
||||
</PopoverCustomTrigger>
|
||||
<PopoverContent
|
||||
className="min-w-max overflow-y-auto p-0 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700"
|
||||
align="start"
|
||||
>
|
||||
<div className="flex flex-col gap-1 p-1">{children}</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SideMenuItem({
|
||||
icon,
|
||||
iconColor,
|
||||
name,
|
||||
to,
|
||||
hasWarning,
|
||||
count,
|
||||
target,
|
||||
subItem = false,
|
||||
}: {
|
||||
icon?: IconNames | React.ComponentType<any>;
|
||||
iconColor?: string;
|
||||
name: string;
|
||||
to: string;
|
||||
hasWarning?: string | boolean;
|
||||
count?: number;
|
||||
target?: AnchorHTMLAttributes<HTMLAnchorElement>["target"];
|
||||
subItem?: boolean;
|
||||
}) {
|
||||
const pathName = usePathName();
|
||||
const isActive = pathName === to;
|
||||
|
||||
return (
|
||||
<LinkButton
|
||||
variant={subItem ? "small-menu-sub-item" : "small-menu-item"}
|
||||
fullWidth
|
||||
textAlignLeft
|
||||
LeadingIcon={icon}
|
||||
leadingIconClassName={isActive ? iconColor : "text-dimmed"}
|
||||
to={to}
|
||||
target={target}
|
||||
className={cn(
|
||||
"text-bright group-hover:bg-slate-850",
|
||||
subItem ? "text-dimmed" : "",
|
||||
isActive ? "bg-slate-850 text-bright" : "group-hover:text-bright"
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between overflow-hidden">
|
||||
<span className="truncate">{name}</span>
|
||||
<div className="flex items-center gap-1">
|
||||
{count !== undefined && count > 0 && <MenuCount count={count} />}
|
||||
{typeof hasWarning === "string" ? (
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<Icon icon={IconExclamationCircle} className="h-5 w-5 text-rose-500" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="flex items-center gap-1 border border-rose-500 bg-rose-500/20 backdrop-blur-xl">
|
||||
{hasWarning}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
hasWarning && <Icon icon={IconExclamationCircle} className="h-5 w-5 text-rose-500" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</LinkButton>
|
||||
);
|
||||
}
|
||||
|
||||
function MenuCount({ count }: { count: number | string }) {
|
||||
return <div className="rounded-full bg-slate-900 px-2 py-1 text-xxs text-dimmed">{count}</div>;
|
||||
}
|
||||
@@ -7,99 +7,156 @@ import { ShortcutKey } from "./ShortcutKey";
|
||||
|
||||
const variant = {
|
||||
"primary/small": {
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80",
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80 px-1",
|
||||
button:
|
||||
"h-6 px-[5px] text-xs bg-indigo-600 group-hover:bg-indigo-500/90 group-disabled:opacity-50 group-disabled:pointer-events-none",
|
||||
icon: "h-3.5",
|
||||
iconSpacing: "gap-x-0.5",
|
||||
shortcutVariant: "small" as const,
|
||||
shortcut:
|
||||
"ml-1 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60 justify-self-center",
|
||||
},
|
||||
"secondary/small": {
|
||||
textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80",
|
||||
textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80 px-1",
|
||||
button:
|
||||
"h-6 px-[5px] text-xs bg-slate-800 group-hover:bg-slate-700/70 disabled:opacity-50 group-disabled:pointer-events-none",
|
||||
icon: "h-3.5",
|
||||
iconSpacing: "gap-x-0.5",
|
||||
shortcutVariant: "small" as const,
|
||||
shortcut:
|
||||
"ml-1 -mr-0.5 border-dimmed/40 text-dimmed group-hover:text-bright/80 group-hover:border-dimmed/60",
|
||||
},
|
||||
"tertiary/small": {
|
||||
textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80",
|
||||
textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80 px-1",
|
||||
button:
|
||||
"h-6 px-[5px] text-xs bg-transparent group-hover:bg-slate-850 disabled:opacity-50 group-disabled:pointer-events-none",
|
||||
icon: "h-3.5",
|
||||
iconSpacing: "gap-x-0.5",
|
||||
shortcutVariant: "small" as const,
|
||||
shortcut:
|
||||
"ml-1 -mr-0.5 border-dimmed/40 text-dimmed group-hover:text-bright/80 group-hover:border-dimmed/60",
|
||||
},
|
||||
"danger/small": {
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80",
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80 px-1",
|
||||
button:
|
||||
"h-6 px-[5px] text-xs bg-rose-600 group-hover:bg-rose-500 disabled:opacity-50 group-disabled:pointer-events-none",
|
||||
icon: "h-3.5",
|
||||
iconSpacing: "gap-x-0.5",
|
||||
shortcutVariant: "small" as const,
|
||||
shortcut: "ml-1 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60",
|
||||
},
|
||||
"primary/medium": {
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80",
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80 px-1",
|
||||
button: "h-8 px-2 text-sm bg-indigo-600 group-hover:bg-indigo-500/90 disabled:opacity-50",
|
||||
icon: "h-4",
|
||||
iconSpacing: "gap-x-0.5",
|
||||
shortcutVariant: "medium" as const,
|
||||
shortcut: "ml-1.5 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60",
|
||||
},
|
||||
"secondary/medium": {
|
||||
textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80",
|
||||
textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80 px-1",
|
||||
button: "h-8 px-2 text-sm bg-slate-800 group-hover:bg-slate-700/70 disabled:opacity-50",
|
||||
icon: "h-4",
|
||||
iconSpacing: "gap-x-0.5",
|
||||
shortcutVariant: "medium" as const,
|
||||
shortcut:
|
||||
"ml-1.5 -mr-0.5 border-dimmed/40 text-dimmed group-hover:border-dimmed group-hover:text-bright",
|
||||
},
|
||||
"tertiary/medium": {
|
||||
textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80",
|
||||
textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80 px-1",
|
||||
button: "h-8 px-2 text-sm bg-transparent group-hover:bg-slate-850 disabled:opacity-50",
|
||||
icon: "h-4",
|
||||
iconSpacing: "gap-x-0.5",
|
||||
shortcutVariant: "medium" as const,
|
||||
shortcut:
|
||||
"ml-1.5 -mr-0.5 border-bright/40 text-dimmed group-hover:border-bright/60 group-hover:text-bright",
|
||||
},
|
||||
"danger/medium": {
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80",
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80 px-1",
|
||||
button: "h-8 px-2 text-sm bg-rose-600 group-hover:bg-rose-500 disabled:opacity-50",
|
||||
icon: "h-4",
|
||||
iconSpacing: "gap-x-0.5",
|
||||
shortcutVariant: "medium" as const,
|
||||
shortcut: "ml-1.5 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60",
|
||||
},
|
||||
"primary/large": {
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-dimmed/80",
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-dimmed/80 px-1",
|
||||
button:
|
||||
"h-10 px-2 text-sm font-medium bg-indigo-600 group-hover:bg-indigo-500/90 disabled:opacity-50",
|
||||
"h-10 px-2 text-sm font-medium bg-indigo-600 group-hover:bg-indigo-500/90 group-disabled:opacity-50",
|
||||
icon: "h-5",
|
||||
iconSpacing: "gap-x-0.5",
|
||||
shortcutVariant: undefined,
|
||||
shortcut: undefined,
|
||||
},
|
||||
"secondary/large": {
|
||||
textColor: "text-dimmed",
|
||||
textColor: "text-dimmed px-1",
|
||||
button:
|
||||
"h-10 px-2 text-sm text-dimmed group-hover:text-bright transition font-medium bg-slate-800 group-hover:bg-slate-700/70 disabled:opacity-50",
|
||||
icon: "h-5",
|
||||
iconSpacing: "gap-x-0.5",
|
||||
shortcutVariant: undefined,
|
||||
shortcut: undefined,
|
||||
},
|
||||
"danger/large": {
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/50",
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/50 px-1",
|
||||
button:
|
||||
"h-10 px-2 text-md bg-rose-600 group-hover:bg-rose-500 group-disabled:opacity-50 group-disabled:group-hover:bg-rose-600",
|
||||
icon: "h-5",
|
||||
iconSpacing: "gap-x-0.5",
|
||||
shortcutVariant: "medium" as const,
|
||||
shortcut: "ml-1.5 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60",
|
||||
},
|
||||
"primary/extra-large": {
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-dimmed/80",
|
||||
button:
|
||||
"h-12 px-2 text-md font-medium bg-indigo-600 group-hover:bg-indigo-500/90 disabled:opacity-50",
|
||||
icon: "h-5",
|
||||
iconSpacing: undefined,
|
||||
shortcutVariant: undefined,
|
||||
shortcut: undefined,
|
||||
},
|
||||
"secondary/extra-large": {
|
||||
textColor: "text-dimmed",
|
||||
button:
|
||||
"h-12 px-2 text-md text-dimmed group-hover:text-bright transition font-medium bg-slate-800 group-hover:bg-slate-700/70 disabled:opacity-50",
|
||||
icon: "h-5",
|
||||
iconSpacing: undefined,
|
||||
shortcutVariant: undefined,
|
||||
shortcut: undefined,
|
||||
},
|
||||
"danger/extra-large": {
|
||||
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/50",
|
||||
button:
|
||||
"h-12 px-2 text-md bg-rose-600 group-hover:bg-rose-500 group-disabled:opacity-50 group-disabled:group-hover:bg-rose-600",
|
||||
icon: "h-5",
|
||||
iconSpacing: undefined,
|
||||
shortcutVariant: "medium" as const,
|
||||
shortcut: "ml-1.5 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60",
|
||||
},
|
||||
"menu-item": {
|
||||
textColor: "text-bright",
|
||||
textColor: "text-bright px-1",
|
||||
button:
|
||||
"h-9 px-[0.475rem] text-sm rounded-sm bg-transparent group-hover:bg-slate-800 transition",
|
||||
icon: "h-5",
|
||||
iconSpacing: "gap-x-0.5",
|
||||
shortcutVariant: undefined,
|
||||
shortcut: undefined,
|
||||
},
|
||||
"small-menu-item": {
|
||||
textColor: "text-bright",
|
||||
button:
|
||||
"h-[1.8rem] px-[0.4rem] text-2sm rounded-sm text-dimmed bg-transparent group-hover:bg-slate-850 transition",
|
||||
icon: "h-4",
|
||||
iconSpacing: "gap-x-1.5",
|
||||
shortcutVariant: undefined,
|
||||
shortcut: undefined,
|
||||
},
|
||||
"small-menu-sub-item": {
|
||||
textColor: "text-dimmed",
|
||||
button:
|
||||
"h-[1.8rem] px-[0.5rem] ml-5 text-2sm rounded-sm text-dimmed bg-transparent group-hover:bg-slate-850 transition",
|
||||
icon: undefined,
|
||||
iconSpacing: undefined,
|
||||
shortcutVariant: undefined,
|
||||
shortcut: undefined,
|
||||
},
|
||||
@@ -110,7 +167,7 @@ const allVariants = {
|
||||
variant: variant,
|
||||
};
|
||||
|
||||
type ButtonContentPropsType = {
|
||||
export type ButtonContentPropsType = {
|
||||
children?: React.ReactNode;
|
||||
LeadingIcon?: React.ComponentType<any> | IconNamesOrString;
|
||||
TrailingIcon?: React.ComponentType<any> | IconNamesOrString;
|
||||
@@ -140,15 +197,17 @@ export function ButtonContent(props: ButtonContentPropsType) {
|
||||
// Based on the size prop, we'll use the corresponding variant classnames
|
||||
const btnClassName = cn(allVariants.$all, variation.button);
|
||||
const iconClassName = variation.icon;
|
||||
const iconSpacingClassName = variation.iconSpacing;
|
||||
const shortcutClassName = variation.shortcut;
|
||||
const textColorClassName = variation.textColor;
|
||||
|
||||
return (
|
||||
<div className={cn(fullWidth ? "flex" : "inline-flex text-xxs", btnClassName, className)}>
|
||||
<div className={cn("flex", fullWidth ? "" : "w-fit text-xxs", btnClassName, className)}>
|
||||
<div
|
||||
className={cn(
|
||||
textAlignLeft ? "text-left" : "justify-center",
|
||||
"flex w-full items-center gap-x-0.5"
|
||||
"flex w-full items-center",
|
||||
iconSpacingClassName
|
||||
)}
|
||||
>
|
||||
{LeadingIcon &&
|
||||
@@ -170,7 +229,7 @@ export function ButtonContent(props: ButtonContentPropsType) {
|
||||
|
||||
{text &&
|
||||
(typeof text === "string" ? (
|
||||
<span className={cn("mx-auto grow self-center truncate px-1", textColorClassName)}>
|
||||
<span className={cn("mx-auto grow self-center truncate", textColorClassName)}>
|
||||
{text}
|
||||
</span>
|
||||
) : (
|
||||
@@ -286,9 +345,13 @@ type NavLinkPropsType = Pick<NavLinkProps, "to" | "target"> &
|
||||
Omit<React.ComponentProps<typeof ButtonContent>, "className"> & {
|
||||
className?: (props: { isActive: boolean; isPending: boolean }) => string | undefined;
|
||||
};
|
||||
export const NavLinkButton = ({ to, className, ...props }: NavLinkPropsType) => {
|
||||
export const NavLinkButton = ({ to, className, target, ...props }: NavLinkPropsType) => {
|
||||
return (
|
||||
<NavLink to={to} className={cn("group outline-none", props.fullWidth ? "w-full" : "")}>
|
||||
<NavLink
|
||||
to={to}
|
||||
className={cn("group outline-none", props.fullWidth ? "w-full" : "")}
|
||||
target={target}
|
||||
>
|
||||
{({ isActive, isPending }) => (
|
||||
<ButtonContent className={className && className({ isActive, isPending })} {...props} />
|
||||
)}
|
||||
|
||||
@@ -13,13 +13,14 @@ import {
|
||||
import { Link } from "@remix-run/react";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Paragraph } from "./Paragraph";
|
||||
import { Spinner } from "./Spinner";
|
||||
|
||||
export const variantClasses = {
|
||||
info: {
|
||||
className: "border-blue-400/20 bg-blue-400/30",
|
||||
icon: <InformationCircleIcon className="h-5 w-5 shrink-0 text-blue-400" />,
|
||||
textColor: "text-blue-200",
|
||||
linkClassName: "transition hover:bg-blue-400/40",
|
||||
className: "border-slate-700 bg-slate-800",
|
||||
icon: <InformationCircleIcon className="h-5 w-5 shrink-0 text-dimmed" />,
|
||||
textColor: "text-bright",
|
||||
linkClassName: "transition hover:bg-slate-750",
|
||||
},
|
||||
warning: {
|
||||
className: "border-yellow-400/20 bg-yellow-400/30",
|
||||
@@ -51,8 +52,16 @@ export const variantClasses = {
|
||||
textColor: "text-blue-200",
|
||||
linkClassName: "transition hover:bg-blue-400/40",
|
||||
},
|
||||
pending: {
|
||||
className: "border-blue-400/20 bg-blue-800/30",
|
||||
icon: <Spinner className="h-5 w-5 shrink-0 " />,
|
||||
textColor: "text-blue-300",
|
||||
linkClassName: "transition hover:bg-blue-400/40",
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type CalloutVariant = keyof typeof variantClasses;
|
||||
|
||||
export function Callout({
|
||||
children,
|
||||
className,
|
||||
@@ -63,7 +72,7 @@ export function Callout({
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
icon?: React.ReactNode;
|
||||
variant: keyof typeof variantClasses;
|
||||
variant: CalloutVariant;
|
||||
to?: string;
|
||||
}) {
|
||||
const variantDefinition = variantClasses[variant];
|
||||
|
||||
@@ -63,16 +63,17 @@ export const DateTimeAccurate = ({ date, timeZone = "UTC" }: DateTimeProps) => {
|
||||
};
|
||||
|
||||
function formatDateTimeAccurate(date: Date, timeZone: string, locales: string[]): string {
|
||||
const milliseconds = `00${date.getMilliseconds()}`.slice(-3);
|
||||
|
||||
const formattedDateTime = new Intl.DateTimeFormat(locales, {
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "2-digit",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
minute: "numeric",
|
||||
second: "numeric",
|
||||
timeZone,
|
||||
// @ts-ignore this works in 92.5% of browsers https://caniuse.com/mdn-javascript_builtins_intl_datetimeformat_datetimeformat_options_parameter_options_fractionalseconddigits_parameter
|
||||
fractionalSecondDigits: 3,
|
||||
}).format(date);
|
||||
|
||||
return `${formatDateTime}.${milliseconds}`;
|
||||
return formattedDateTime;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Icon, IconInBox, RenderIcon } from "./Icon";
|
||||
import { Paragraph } from "./Paragraph";
|
||||
|
||||
const variations = {
|
||||
small: {
|
||||
label: {
|
||||
variant: "small" as const,
|
||||
className: "m-0 leading-[1.1rem]",
|
||||
},
|
||||
description: {
|
||||
variant: "extra-small" as const,
|
||||
className: "m-0",
|
||||
},
|
||||
},
|
||||
base: {
|
||||
label: {
|
||||
variant: "base" as const,
|
||||
className: "m-0 leading-[1.1rem] ",
|
||||
},
|
||||
description: {
|
||||
variant: "small" as const,
|
||||
className: "m-0",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
type DetailCellProps = {
|
||||
leadingIcon?: RenderIcon;
|
||||
leadingIconClassName?: string;
|
||||
trailingIcon?: RenderIcon;
|
||||
trailingIconClassName?: string;
|
||||
label: string | React.ReactNode;
|
||||
description?: string | React.ReactNode;
|
||||
className?: string;
|
||||
variant?: keyof typeof variations;
|
||||
};
|
||||
|
||||
export function DetailCell({
|
||||
leadingIcon,
|
||||
leadingIconClassName,
|
||||
trailingIcon,
|
||||
trailingIconClassName,
|
||||
label,
|
||||
description,
|
||||
className,
|
||||
variant = "small",
|
||||
}: DetailCellProps) {
|
||||
const variation = variations[variant];
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group flex h-11 w-full items-center gap-3 rounded-md p-1 pr-3 transition hover:bg-slate-900",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<IconInBox
|
||||
icon={leadingIcon}
|
||||
className={cn("flex-none transition group-hover:border-slate-750", leadingIconClassName)}
|
||||
/>
|
||||
<div className="flex flex-1 flex-col">
|
||||
<Paragraph
|
||||
variant={variation.label.variant}
|
||||
className={cn(
|
||||
"flex-1 text-left transition group-hover:text-bright",
|
||||
variation.label.className
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
</Paragraph>
|
||||
{description && (
|
||||
<Paragraph
|
||||
variant={variation.description.variant}
|
||||
className={cn(
|
||||
"flex-1 text-left text-dimmed transition group-hover:text-bright",
|
||||
variation.description.className
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
</Paragraph>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-none items-center gap-1">
|
||||
<Icon
|
||||
icon={trailingIcon}
|
||||
className={cn(
|
||||
"h-6 w-6 flex-none transition group-hover:border-slate-750",
|
||||
trailingIconClassName
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -43,12 +43,12 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed z-50 grid w-full gap-4 rounded-b-lg border bg-midnight-800 p-6 pt-11 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
|
||||
"fixed z-50 grid w-full gap-4 rounded-b-lg border bg-midnight-800 px-4 pb-6 pt-3 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<hr className="-ml-6 w-[calc(100%_+_3rem)]" />
|
||||
<hr className="absolute left-0 top-12 w-full" />
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-3 top-3 rounded-sm opacity-70 ring-offset-background transition-opacity data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none">
|
||||
<div className="flex gap-x-2">
|
||||
@@ -73,7 +73,7 @@ const DialogContent = React.forwardRef<
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
|
||||
<div className={cn("flex flex-col text-left font-medium text-dimmed", className)} {...props} />
|
||||
);
|
||||
DialogHeader.displayName = "DialogHeader";
|
||||
|
||||
|
||||
@@ -2,8 +2,17 @@ import type { z } from "zod";
|
||||
import { Paragraph } from "./Paragraph";
|
||||
import { NamedIcon } from "./NamedIcon";
|
||||
import { motion } from "framer-motion";
|
||||
import { cn } from "~/utils/cn";
|
||||
|
||||
export function FormError({ children, id }: { children: React.ReactNode; id?: string }) {
|
||||
export function FormError({
|
||||
children,
|
||||
id,
|
||||
className,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
id?: string;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{children && (
|
||||
@@ -11,7 +20,7 @@ export function FormError({ children, id }: { children: React.ReactNode; id?: st
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className="flex items-start gap-0.5"
|
||||
className={cn("flex items-start gap-0.5", className)}
|
||||
>
|
||||
<NamedIcon name="error" className="h-4 w-4 shrink-0 justify-start" />
|
||||
<Paragraph id={id} variant="extra-small" className="text-rose-500">
|
||||
|
||||
@@ -21,7 +21,7 @@ export function FormTitle({
|
||||
<div
|
||||
className={cn(
|
||||
"mb-4 flex flex-col gap-3 pb-4",
|
||||
divide ? "border-b border-uiBorder" : "",
|
||||
divide ? "border-b border-ui-border" : "",
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { cn } from "~/utils/cn";
|
||||
|
||||
const headerVariants = {
|
||||
header1: {
|
||||
text: "font-sans text-base md:text-lg lg:text-xl leading-5 md:leading-6 lg:leading-7 font-semibold",
|
||||
text: "font-sans text-2xl leading-5 md:leading-6 lg:leading-7 font-semibold",
|
||||
spacing: "mb-2",
|
||||
},
|
||||
header2: {
|
||||
|
||||
@@ -88,12 +88,7 @@ export function HelpContent({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="grow rounded border border-slate-850 bg-midnight-850 bg-contain bg-left-top bg-no-repeat p-6"
|
||||
style={{
|
||||
backgroundImage: `url(${gradientPath})`,
|
||||
}}
|
||||
>
|
||||
<div className="grow rounded border border-slate-850 bg-midnight-850/80 bg-contain bg-left-top bg-no-repeat p-6">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { IconNamesOrString, NamedIcon } from "./NamedIcon";
|
||||
import { cn } from "~/utils/cn";
|
||||
|
||||
export type RenderIcon = IconNamesOrString | React.ComponentType<any>;
|
||||
|
||||
type IconProps = {
|
||||
icon?: RenderIcon;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/** Use this icon to either render a passed in React component, or a NamedIcon/CompanyIcon */
|
||||
export function Icon(props: IconProps) {
|
||||
if (typeof props.icon === "string") {
|
||||
return <NamedIcon name={props.icon} className={props.className ?? ""} fallback={<></>} />;
|
||||
}
|
||||
|
||||
const Icon = props.icon;
|
||||
|
||||
if (!Icon) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return <Icon className={props.className} />;
|
||||
}
|
||||
|
||||
export function IconInBox({ boxClassName, ...props }: IconProps & { boxClassName?: string }) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"grid h-9 w-9 place-content-center rounded-sm border border-slate-750 bg-slate-850",
|
||||
boxClassName
|
||||
)}
|
||||
>
|
||||
<Icon icon={props.icon} className={cn("h-6 w-6", props.className)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import { NamedIcon } from "./NamedIcon";
|
||||
const variants = {
|
||||
large: {
|
||||
input:
|
||||
"px-3 flex h-10 w-full text-bright rounded-md border border-slate-800 bg-slate-850 text-sm ring-offset-background transition file:border-0 file:bg-transparent file:text-base file:font-medium placeholder:text-muted-foreground hover:border-slate-750 hover:bg-slate-800 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-0 focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"px-3 flex h-10 w-full text-bright rounded-[3px] border border-slate-800 bg-slate-850 text-sm ring-offset-background transition file:border-0 file:bg-transparent file:text-base file:font-medium placeholder:text-muted-foreground hover:border-slate-750 hover:bg-slate-800 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-0 focus:border-ring focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
|
||||
iconSize: "h-4 w-4 ml-3",
|
||||
iconOffset: "pl-[34px]",
|
||||
@@ -33,7 +33,7 @@ const variants = {
|
||||
},
|
||||
tertiary: {
|
||||
input:
|
||||
"px-1 flex h-6 w-full text-bright rounded bg-transparent border border-transparent transition hover:border-slate-800 hover:bg-slate-850 focus:border-slate-800 focus:bg-slate-850 text-xs ring-offset-background transition file:border-0 file:bg-transparent file:text-xs file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"px-1 flex h-6 w-full text-bright rounded bg-transparent border border-transparent hover:border-slate-800 hover:bg-slate-850 focus:border-slate-800 focus:bg-slate-850 text-xs ring-offset-background transition file:border-0 file:bg-transparent file:text-xs file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
|
||||
iconSize: "h-3 w-3 ml-1.5",
|
||||
iconOffset: "pl-[21px]",
|
||||
|
||||
@@ -2,6 +2,7 @@ import { cn } from "~/utils/cn";
|
||||
import { Paragraph } from "./Paragraph";
|
||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/20/solid";
|
||||
import { SimpleTooltip } from "./Tooltip";
|
||||
import { Link } from "@remix-run/react";
|
||||
|
||||
const variations = {
|
||||
primary: {
|
||||
@@ -45,18 +46,7 @@ export function LabelValueStack({
|
||||
<Paragraph variant={variation.label}>{label}</Paragraph>
|
||||
<>
|
||||
{href ? (
|
||||
<SimpleTooltip
|
||||
side="bottom"
|
||||
button={
|
||||
<Paragraph variant={variation.value}>
|
||||
<a href={href} className="underline underline-offset-2" target="_blank">
|
||||
{value}
|
||||
<ArrowTopRightOnSquareIcon className="ml-1 inline-block h-4 w-4 text-dimmed" />
|
||||
</a>
|
||||
</Paragraph>
|
||||
}
|
||||
content={href}
|
||||
/>
|
||||
<ValueButton value={value} href={href} variant={variant} />
|
||||
) : (
|
||||
<Paragraph variant={variation.value}>{value}</Paragraph>
|
||||
)}
|
||||
@@ -64,3 +54,40 @@ export function LabelValueStack({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type ValueButtonStackProps = {
|
||||
value: React.ReactNode;
|
||||
href: string;
|
||||
variant?: keyof typeof variations;
|
||||
};
|
||||
|
||||
function ValueButton({ value, href, variant = "secondary" }: ValueButtonStackProps) {
|
||||
const variation = variations[variant];
|
||||
|
||||
const isExternalUrl = href.startsWith("http");
|
||||
|
||||
if (!isExternalUrl) {
|
||||
return (
|
||||
<Paragraph variant={variation.value}>
|
||||
<Link to={href} reloadDocument className="underline underline-offset-2">
|
||||
{value}
|
||||
</Link>
|
||||
</Paragraph>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SimpleTooltip
|
||||
side="bottom"
|
||||
button={
|
||||
<Paragraph variant={variation.value}>
|
||||
<a href={href} className="underline underline-offset-2" target="_blank">
|
||||
{value}
|
||||
<ArrowTopRightOnSquareIcon className="ml-1 inline-block h-4 w-4 text-dimmed" />
|
||||
</a>
|
||||
</Paragraph>
|
||||
}
|
||||
content={href}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
ChevronUpIcon,
|
||||
ClipboardDocumentCheckIcon,
|
||||
ClockIcon,
|
||||
CloudIcon,
|
||||
CodeBracketSquareIcon,
|
||||
@@ -34,12 +35,15 @@ import {
|
||||
GlobeAltIcon,
|
||||
HandRaisedIcon,
|
||||
HeartIcon,
|
||||
HomeIcon,
|
||||
KeyIcon,
|
||||
LightBulbIcon,
|
||||
ListBulletIcon,
|
||||
MagnifyingGlassIcon,
|
||||
PlusIcon,
|
||||
PlusSmallIcon,
|
||||
QrCodeIcon,
|
||||
Square2StackIcon,
|
||||
SquaresPlusIcon,
|
||||
StarIcon,
|
||||
TrashIcon,
|
||||
@@ -47,6 +51,7 @@ import {
|
||||
UserGroupIcon,
|
||||
UserIcon,
|
||||
UserPlusIcon,
|
||||
WindowIcon,
|
||||
WrenchScrewdriverIcon,
|
||||
XCircleIcon,
|
||||
XMarkIcon,
|
||||
@@ -54,15 +59,19 @@ import {
|
||||
import { CompanyIcon, hasIcon } from "@trigger.dev/companyicons";
|
||||
import { ActivityIcon, HourglassIcon } from "lucide-react";
|
||||
import { DynamicTriggerIcon } from "~/assets/icons/DynamicTriggerIcon";
|
||||
import { EndpointIcon } from "~/assets/icons/EndpointIcon";
|
||||
import { ErrorIcon } from "~/assets/icons/ErrorIcon";
|
||||
import { OneTreeIcon } from "~/assets/icons/OneTreeIcon";
|
||||
import { RunsIcon } from "~/assets/icons/RunsIcon";
|
||||
import { SaplingIcon } from "~/assets/icons/SaplingIcon";
|
||||
import { ScheduleIcon } from "~/assets/icons/ScheduleIcon";
|
||||
import { TwoTreesIcon } from "~/assets/icons/TwoTreesIcon";
|
||||
import { WebhookIcon } from "~/assets/icons/WebhookIcon";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { tablerIcons } from "~/utils/tablerIcons";
|
||||
import { LogoIcon } from "../LogoIcon";
|
||||
import { Spinner } from "./Spinner";
|
||||
import { SaplingIcon } from "~/assets/icons/SaplingIcon";
|
||||
import { TwoTreesIcon } from "~/assets/icons/TwoTreesIcon";
|
||||
import { OneTreeIcon } from "~/assets/icons/OneTreeIcon";
|
||||
import tablerSpritePath from "./tabler-sprite.svg";
|
||||
|
||||
const icons = {
|
||||
account: (className: string) => <UserCircleIcon className={cn("text-slate-400", className)} />,
|
||||
@@ -71,7 +80,9 @@ const icons = {
|
||||
"arrow-left": (className: string) => <ArrowLeftIcon className={cn("text-white", className)} />,
|
||||
background: (className: string) => <CloudIcon className={cn("text-sky-400", className)} />,
|
||||
beaker: (className: string) => <BeakerIcon className={cn("text-purple-500", className)} />,
|
||||
bell: (className: string) => <BellAlertIcon className={cn("text-amber-500", className)} />,
|
||||
billing: (className: string) => <CreditCardIcon className={cn("text-teal-500", className)} />,
|
||||
browser: (className: string) => <WindowIcon className={cn("text-dimmed", className)} />,
|
||||
calendar: (className: string) => (
|
||||
<CalendarDaysIcon className={cn("text-purple-500", className)} />
|
||||
),
|
||||
@@ -108,6 +119,7 @@ const icons = {
|
||||
<HandRaisedIcon className={cn("text-amber-400", className)} />
|
||||
),
|
||||
heart: (className: string) => <HeartIcon className={cn("text-rose-500", className)} />,
|
||||
house: (className: string) => <HomeIcon className={cn("text-dimmed", className)} />,
|
||||
id: (className: string) => <FingerPrintIcon className={cn("text-rose-200", className)} />,
|
||||
inactive: (className: string) => <XCircleIcon className={cn("text-rose-500", className)} />,
|
||||
info: (className: string) => <InformationCircleIcon className={cn("text-blue-500", className)} />,
|
||||
@@ -117,9 +129,15 @@ const icons = {
|
||||
"invite-member": (className: string) => (
|
||||
<UserPlusIcon className={cn("text-indigo-500", className)} />
|
||||
),
|
||||
job: (className: string) => <WrenchScrewdriverIcon className={cn("text-teal-500", className)} />,
|
||||
job: (className: string) => (
|
||||
<WrenchScrewdriverIcon className={cn("text-indigo-500", className)} />
|
||||
),
|
||||
key: (className: string) => <KeyIcon className={cn("text-amber-400", className)} />,
|
||||
lightbulb: (className: string) => <LightBulbIcon className={cn("text-amber-400", className)} />,
|
||||
"clipboard-checked": (className: string) => (
|
||||
<ClipboardDocumentCheckIcon className={cn("text-dimmed", className)} />
|
||||
),
|
||||
list: (className: string) => <ListBulletIcon className={cn("text-slate-400", className)} />,
|
||||
log: (className: string) => (
|
||||
<ChatBubbleLeftEllipsisIcon className={cn("text-slate-400", className)} />
|
||||
),
|
||||
@@ -140,6 +158,7 @@ const icons = {
|
||||
settings: (className: string) => <Cog8ToothIcon className={cn("text-slate-600", className)} />,
|
||||
spinner: (className: string) => <Spinner className={className} color="blue" />,
|
||||
"spinner-white": (className: string) => <Spinner className={className} color="white" />,
|
||||
squares: (className: string) => <Square2StackIcon className={cn("text-dimmed", className)} />,
|
||||
star: (className: string) => <StarIcon className={cn("text-yellow-500", className)} />,
|
||||
stop: (className: string) => <StopIcon className={cn("text-rose-500", className)} />,
|
||||
team: (className: string) => <UserGroupIcon className={cn("text-blue-500", className)} />,
|
||||
@@ -168,6 +187,11 @@ const icons = {
|
||||
<ScheduleIcon className={cn("text-sky-500", className)} />
|
||||
),
|
||||
webhook: (className: string) => <WebhookIcon className={cn("text-pink-500", className)} />,
|
||||
endpoint: (className: string) => <EndpointIcon className={cn("text-blue-500", className)} />,
|
||||
"http-endpoint": (className: string) => (
|
||||
<GlobeAltIcon className={cn("text-blue-500", className)} />
|
||||
),
|
||||
runs: (className: string) => <RunsIcon className={cn("text-lime-500", className)} />,
|
||||
};
|
||||
|
||||
export type IconNames = keyof typeof icons;
|
||||
@@ -202,7 +226,15 @@ export function NamedIcon({
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`Icon ${name} not found`);
|
||||
if (tablerIcons.has("tabler-" + name)) {
|
||||
return <TablerIcon name={"tabler-" + name} className={className} />;
|
||||
} else if (name.startsWith("tabler-") && tablerIcons.has(name)) {
|
||||
return <TablerIcon name={name} className={className} />;
|
||||
}
|
||||
|
||||
if (name === "supabase-management") {
|
||||
return <NamedIcon name="supabase" className={className} />;
|
||||
}
|
||||
|
||||
if (fallback) {
|
||||
return fallback;
|
||||
@@ -234,3 +266,11 @@ export function NamedIconInBox({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TablerIcon({ name, className }: { name: string; className?: string }) {
|
||||
return (
|
||||
<svg className={cn("stroke-[1.5]", className)}>
|
||||
<use xlinkHref={`${tablerSpritePath}#${name}`} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import { Paragraph } from "./Paragraph";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { NamedIcon } from "./NamedIcon";
|
||||
import { Tabs, TabsProps } from "./Tabs";
|
||||
import { Icon, RenderIcon } from "./Icon";
|
||||
import { Button, LinkButton } from "./Buttons";
|
||||
import { ArrowUpRightIcon } from "@heroicons/react/20/solid";
|
||||
|
||||
type WithChildren = {
|
||||
children: React.ReactNode;
|
||||
@@ -13,7 +16,7 @@ type WithChildren = {
|
||||
|
||||
export function PageHeader({ children, hideBorder }: WithChildren & { hideBorder?: boolean }) {
|
||||
return (
|
||||
<div className={cn("mx-4 pt-4", hideBorder ? "" : "border-b border-uiBorder pb-4")}>
|
||||
<div className={cn("mx-4 pt-4", hideBorder ? "" : "border-b border-ui-border pb-4")}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -24,6 +27,7 @@ export function PageTitleRow({ children }: WithChildren) {
|
||||
}
|
||||
|
||||
type PageTitleProps = {
|
||||
icon?: RenderIcon;
|
||||
title: string;
|
||||
backButton?: {
|
||||
to: string;
|
||||
@@ -31,7 +35,7 @@ type PageTitleProps = {
|
||||
};
|
||||
};
|
||||
|
||||
export function PageTitle({ title, backButton }: PageTitleProps) {
|
||||
export function PageTitle({ icon, title, backButton }: PageTitleProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{backButton && (
|
||||
@@ -48,7 +52,10 @@ export function PageTitle({ title, backButton }: PageTitleProps) {
|
||||
<BreadcrumbIcon className="h-6" />
|
||||
</div>
|
||||
)}
|
||||
<Header1>{title}</Header1>
|
||||
<Header1 className="flex items-center gap-1">
|
||||
{icon && <Icon icon={icon} className="h-5 w-5" />}
|
||||
{title}
|
||||
</Header1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -89,20 +96,43 @@ export function PageInfoProperty({
|
||||
icon,
|
||||
label,
|
||||
value,
|
||||
to,
|
||||
}: {
|
||||
icon?: string | React.ReactNode;
|
||||
label?: string;
|
||||
value: React.ReactNode;
|
||||
value?: React.ReactNode;
|
||||
to?: string;
|
||||
}) {
|
||||
if (to === undefined) {
|
||||
return <PageInfoPropertyContent icon={icon} label={label} value={value} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<LinkButton variant="tertiary/small" to={to} TrailingIcon={ArrowUpRightIcon}>
|
||||
<PageInfoPropertyContent icon={icon} label={label} value={value} />
|
||||
</LinkButton>
|
||||
);
|
||||
}
|
||||
|
||||
function PageInfoPropertyContent({
|
||||
icon,
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
icon?: string | React.ReactNode;
|
||||
label?: string;
|
||||
value?: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
{icon && typeof icon === "string" ? <NamedIcon name={icon} className="h-4 w-4" /> : icon}
|
||||
{label && (
|
||||
<Paragraph variant="extra-small/caps" className="mt-0.5 whitespace-nowrap">
|
||||
{label}:
|
||||
{label}
|
||||
{value && ":"}
|
||||
</Paragraph>
|
||||
)}
|
||||
<Paragraph variant="small">{value}</Paragraph>
|
||||
{value && <Paragraph variant="small">{value}</Paragraph>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user