diff --git a/.env.example b/.env.example
index 9d002d9..52befe0 100644
--- a/.env.example
+++ b/.env.example
@@ -1,34 +1,33 @@
-# .env.example
-NEXT_PUBLIC_URL=""
-APP_URL=""
-NEXTAUTH_URL=""
+NEXTAUTH_URL="http://localhost:3000"
+OKTA_OAUTH2_CLIENT_ID=
+OKTA_OAUTH2_CLIENT_SECRET=
+OKTA_OAUTH2_ISSUER=
+OKTA_API_KEY=
+SECRET=
+NEXT_PUBLIC_URL=
# Twilio Configurations
# Found at https://console.twilio.com/us1/account/keys-credentials/api-keys under "Live credentials"
-TWILIO_ACCOUNT_SID=""
-TWILIO_AUTH_TOKEN=""
-
+TWILIO_ACCOUNT_SID=
+# Found at https://console.twilio.com/us1/develop/phone-numbers/manage/twiml-apps
+TWILIO_TWIML_APP_SID=
+TWILIO_CALLER_ID=
# Found at https://console.twilio.com/us1/account/keys-credentials/api-keys under "API keys" as SID
# Create an API key if one doesn't exist. More instructions at https://www.twilio.com/docs/glossary/what-is-an-api-key
-TWILIO_API_KEY=""
+TWILIO_API_KEY=
# When you create the API key, you’ll be shown its secret, which is the variable below
# For security, you will only be shown the secret at this time so store it in a secure location
-TWILIO_API_SECRET=""
-
-# Found at https://console.twilio.com/us1/develop/phone-numbers/manage/twiml-apps
-TWILIO_TWIML_APP_SID=""
-TWILIO_CALLER_ID=""
-
-# Found at https://www.twilio.com/console/sync/services
-TWILIO_SYNC_SERVICE_SID=""
-
-NEXT_PUBLIC_WORKSPACE_SID=""
-
-NEXT_PUBLIC_OKTA_URL=""
+TWILIO_API_SECRET=
+TWILIO_AUTH_TOKEN=
+TWILIO_WORKSPACE_SID=
+TWILIO_WORKFLOW_SID=
+TWILIO_TASKQUEUE_SID=
+# Is this hardcoded or do we dynamically get it?
# Airtable test tokens, has to be generated with https://airtable.com/create/tokens
# TEST_BASE_ID and TEST_TABLE_ID can be found in the url of the target airtable table e.g.
# https://airtable.com/{BASE_ID}/{TABLE_ID}
-AIRTABLE_TEST_API_KEY=your_api_key
-AIRTABLE_TEST_BASE_ID=starts_with_app
-AIRTABLE_TEST_TABLE_ID=your_table_id
+AIRTABLE_TEST_API_KEY=
+AIRTABLE_TEST_BASE_ID=
+AIRTABLE_TEST_TABLE_ID=
+
diff --git a/.eslintrc.json b/.eslintrc.json
index bffb357..f1db1de 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,3 +1,18 @@
{
- "extends": "next/core-web-vitals"
+ "env": {
+ "es2021": true
+ },
+ "extends": [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:react/recommended",
+ "next/core-web-vitals",
+ "prettier"
+ ],
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "ecmaVersion": "latest",
+ "sourceType": "module"
+ },
+ "plugins": ["@typescript-eslint", "react"]
}
diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md
index ad1251d..44ebfd1 100644
--- a/.github/ISSUE_TEMPLATE/bug-report.md
+++ b/.github/ISSUE_TEMPLATE/bug-report.md
@@ -1,32 +1,33 @@
---
name: Bug report
about: Create a report to help us improve
-title: "[Bug]"
+title: '[Bug]'
labels: bug
assignees: israelhhh2
-
---
# Description
-*A clear and concise description of what the bug is.*
+_A clear and concise description of what the bug is._
# Expected Behavior
-*A clear and concise description of what you expected to happen.*
+_A clear and concise description of what you expected to happen._
# How to Reproduce
Steps to reproduce the behavior:
+
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
## Runtime Environment
- - OS and version: [e.g. Windows 11/macOS Sonoma/Android 12/etc.]
- - Browser and Version [e.g. Chrome 121]
+
+- OS and version: [e.g. Windows 11/macOS Sonoma/Android 12/etc.]
+- Browser and Version [e.g. Chrome 121]
# Additional context
-*Add any other context about the problem here. If applicable, add screenshots to help explain your problem. Remove if not needed.*
+_Add any other context about the problem here. If applicable, add screenshots to help explain your problem. Remove if not needed._
diff --git a/.github/ISSUE_TEMPLATE/internal-task.md b/.github/ISSUE_TEMPLATE/internal-task.md
index 78b8f6c..b875b31 100644
--- a/.github/ISSUE_TEMPLATE/internal-task.md
+++ b/.github/ISSUE_TEMPLATE/internal-task.md
@@ -1,6 +1,6 @@
---
name: Internal Task
-about: Generic issue for detailed work that needs to be done that isn't necessarily user-facing
+about: Generic issue for detailed work that needs to be done that isn't necessarily user-facing
title: '[Internal]'
labels: internal
assignees: ''
@@ -8,20 +8,20 @@ assignees: ''
# Overview
-*Describe the nature of this issue at a high level.*
+_Describe the nature of this issue at a high level._
# Acceptance Criteria
-*Describe what specific conditions must be met for this issue to be resolved.*
+_Describe what specific conditions must be met for this issue to be resolved._
- [ ] Criteria 1
# Tasks
-*If applicable, include a recommended set of non-user-facing technical tasks needed for a developer to implement this issue.*
+_If applicable, include a recommended set of non-user-facing technical tasks needed for a developer to implement this issue._
- [ ] Task 1
# Additional Info
-*If there are any additional documents or supplementary materials to resolve this issue, link them here. Remove if not needed.*
+_If there are any additional documents or supplementary materials to resolve this issue, link them here. Remove if not needed._
diff --git a/.github/ISSUE_TEMPLATE/user-story.md b/.github/ISSUE_TEMPLATE/user-story.md
index 4e1c260..5d3ba50 100644
--- a/.github/ISSUE_TEMPLATE/user-story.md
+++ b/.github/ISSUE_TEMPLATE/user-story.md
@@ -4,20 +4,19 @@ about: Generic user story template for a new user-facing feature
title: ''
labels: feature
assignees: ''
-
---
**As a [USER TYPE], I should be able to [TASK] so I can [REASON].**
# Acceptance Criteria
-*Describe what conditions must be met for this issue to be resolved.*
+_Describe what conditions must be met for this issue to be resolved._
- [ ] Criteria 1
# Tasks
-*If applicable, include a recommended set of non-user-facing technical tasks needed for a developer to implement this issue.*
+_If applicable, include a recommended set of non-user-facing technical tasks needed for a developer to implement this issue._
- [ ] Task 1
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 6b6601d..789b344 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,9 +1,9 @@
# Overview
-*Closes #X, closes, #Y*
+_Closes #X, closes, #Y_
-*At a high-level, describe what changed. Details should already be included in the linked issues.*
+_At a high-level, describe what changed. Details should already be included in the linked issues._
# Other Notes
-*If there is important information that isn't immediately obvious from the changes that were made, include that info here. Remove this section if not needed.*
+_If there is important information that isn't immediately obvious from the changes that were made, include that info here. Remove this section if not needed._
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 4d9f0ec..06ced5b 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -16,7 +16,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: 20
-
+
- name: Set up pnpm
uses: pnpm/action-setup@v2
with:
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000..32fd7b2
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,11 @@
+module.exports = {
+ printWidth: 80,
+ tabWidth: 2,
+ trailingComma: 'all',
+ singleQuote: true,
+ semi: true,
+ importOrder: ['^@/(.*)$', '^[./]'],
+ importOrderSeparation: true,
+ importOrderSortSpecifiers: true,
+ plugins: ['@trivago/prettier-plugin-sort-imports'],
+};
diff --git a/README.md b/README.md
index 364b791..c930104 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,17 @@
-## Getting Started
+# Connie
-Install [pnpm](https://pnpm.io/installation)
+## Development
-(For example, using homebrew:)
+This project uses pnpm as its package manager. Make sure is it [installed](https://pnpm.io/installation)
+before continuing.
-```
-brew install pnpm
-```
+#### Aside: Why pnpm?
+
+We chose to use pnpm for its improved security over npm, and also its improved
+package management speed compared to yarn. Read more [here](https://hackernoon.com/choosing-the-right-package-manager-npm-yarn-or-pnpm).
-Create a configuration file for your application by copying the .env.example and edit the .env file with the appropriate values
+Create a configuration file for your application by copying the .env.example and
+edit the .env file with the appropriate values:
```bash
cp .env.example .env
@@ -25,41 +28,34 @@ Open [http://localhost:3000](http://localhost:3000) with your browser to see the
You can start editing the page by modifying `src/app/page.tsx`. The page auto-updates as you edit the file.
-This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
+### Format
-## Learn More
+Before committing, make sure to format your code with the following command:
-### Next.js
-
-To learn more about Next.js, take a look at the following resources:
-
-- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+```bash
+pnpm format
+```
-You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
+### Environment Setup
-### Why pnpm?
+#### Environment Variables
-We chose to use pnpm for its improved security over npm, and also its improved package management speed compared to yarn. Read more [here](https://hackernoon.com/choosing-the-right-package-manager-npm-yarn-or-pnpm).
+Create a .env file at the root of the repository with the following variables:
+NEXTAUTH_URL=http://localhost:3000
+OKTA_OAUTH2_CLIENT_ID={OKTA APPLICATION CLIENT ID}
+OKTA_OAUTH2_CLIENT_SECRET={OKTA APPLICATION SECRET}
+OKTA_OAUTH2_ISSUER=https://{YOUR OKTA ACCOUNT URL (click top right and it will appear below your email)}
+SECRET=Some long random string
-### Deployment
+## Deployment
View the latest deployment branch [here](https://develop.d2r9j66448p933.amplifyapp.com/).
This project has been integrated with the AWS amplify github app for automatic deployments. All commits and pull requests to the `develop` branch have been set up for automatic deployment. Deployment and build settings should be edited through the AWS Amplify [console](https://console.aws.amazon.com/amplify/home) and through associated [documentation](https://docs.aws.amazon.com/amplify/latest/userguide/getting-started.html).
Deployment build setting specifications:
+
- Changed node version to 20 to be compatible with building next.js applications to fulfil requirement >=18.17.0
- Changed container image to be Amazon linux 2023 to solve [GLIBC_2.28 not found](https://stackoverflow.com/questions/72921215/getting-glibc-2-28-not-found) error after upgrading node version.
- Added npmrc file as a linker between pnpm and npm because amplify does some part of its installation using npm [here](https://docs.aws.amazon.com/amplify/latest/userguide/monorepo-configuration.html#turborepo-pnpm-monorepo-configuration).
- Since repository is public, AWS amplify app has no linked IAM service roles for [security purposes](https://docs.aws.amazon.com/amplify/latest/userguide/pr-previews.html) to enable pull request previews.
-
-## Environment Setup
-
-### Environment Variables
-Create a .env file at the root of the repository with the following variables:
-NEXTAUTH_URL=http://localhost:3000
-OKTA_OAUTH2_CLIENT_ID={OKTA APPLICATION CLIENT ID}
-OKTA_OAUTH2_CLIENT_SECRET={OKTA APPLICATION SECRET}
-OKTA_OAUTH2_ISSUER=https://{YOUR OKTA ACCOUNT URL (click top right and it will appear below your email)}
-SECRET=Some long random string
diff --git a/next.config.js b/next.config.js
index 767719f..658404a 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,4 +1,4 @@
/** @type {import('next').NextConfig} */
-const nextConfig = {}
+const nextConfig = {};
-module.exports = nextConfig
+module.exports = nextConfig;
diff --git a/package.json b/package.json
index 112899b..4eb66d7 100644
--- a/package.json
+++ b/package.json
@@ -46,15 +46,20 @@
"xlsx": "^0.18.5"
},
"devDependencies": {
+ "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
+ "@typescript-eslint/eslint-plugin": "^7.0.2",
+ "@typescript-eslint/parser": "^7.0.2",
"autoprefixer": "^10.0.1",
"eslint": "^8",
"eslint-config-next": "14.0.4",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-react": "^7.33.2",
"postcss": "^8",
"prettier": "3.2.5",
- "tailwindcss": "^3.3.0",
+ "tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2d63dfb..6536ada 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -97,7 +97,7 @@ dependencies:
version: 2.2.0
tailwindcss-animate:
specifier: ^1.0.7
- version: 1.0.7(tailwindcss@3.3.6)
+ version: 1.0.7(tailwindcss@3.4.1)
twilio:
specifier: ^4.20.1
version: 4.20.1
@@ -109,6 +109,9 @@ dependencies:
version: 0.18.5
devDependencies:
+ '@trivago/prettier-plugin-sort-imports':
+ specifier: ^4.3.0
+ version: 4.3.0(prettier@3.2.5)
'@types/node':
specifier: ^20
version: 20.10.4
@@ -118,6 +121,12 @@ devDependencies:
'@types/react-dom':
specifier: ^18
version: 18.2.17
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^7.0.2
+ version: 7.0.2(@typescript-eslint/parser@7.0.2)(eslint@8.55.0)(typescript@5.3.3)
+ '@typescript-eslint/parser':
+ specifier: ^7.0.2
+ version: 7.0.2(eslint@8.55.0)(typescript@5.3.3)
autoprefixer:
specifier: ^10.0.1
version: 10.4.16(postcss@8.4.32)
@@ -127,6 +136,12 @@ devDependencies:
eslint-config-next:
specifier: 14.0.4
version: 14.0.4(eslint@8.55.0)(typescript@5.3.3)
+ eslint-config-prettier:
+ specifier: ^9.1.0
+ version: 9.1.0(eslint@8.55.0)
+ eslint-plugin-react:
+ specifier: ^7.33.2
+ version: 7.33.2(eslint@8.55.0)
postcss:
specifier: ^8
version: 8.4.32
@@ -134,8 +149,8 @@ devDependencies:
specifier: 3.2.5
version: 3.2.5
tailwindcss:
- specifier: ^3.3.0
- version: 3.3.6
+ specifier: ^3.4.1
+ version: 3.4.1
typescript:
specifier: ^5
version: 5.3.3
@@ -782,12 +797,137 @@ packages:
tslib: 2.6.2
dev: false
+ /@babel/code-frame@7.23.5:
+ resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/highlight': 7.23.4
+ chalk: 2.4.2
+ dev: true
+
+ /@babel/generator@7.17.7:
+ resolution: {integrity: sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.17.0
+ jsesc: 2.5.2
+ source-map: 0.5.7
+ dev: true
+
+ /@babel/generator@7.23.6:
+ resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.9
+ '@jridgewell/gen-mapping': 0.3.3
+ '@jridgewell/trace-mapping': 0.3.20
+ jsesc: 2.5.2
+ dev: true
+
+ /@babel/helper-environment-visitor@7.22.20:
+ resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-function-name@7.23.0:
+ resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/template': 7.23.9
+ '@babel/types': 7.23.9
+ dev: true
+
+ /@babel/helper-hoist-variables@7.22.5:
+ resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.9
+ dev: true
+
+ /@babel/helper-split-export-declaration@7.22.6:
+ resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.9
+ dev: true
+
+ /@babel/helper-string-parser@7.23.4:
+ resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-validator-identifier@7.22.20:
+ resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/highlight@7.23.4:
+ resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-validator-identifier': 7.22.20
+ chalk: 2.4.2
+ js-tokens: 4.0.0
+ dev: true
+
+ /@babel/parser@7.23.9:
+ resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+ dependencies:
+ '@babel/types': 7.17.0
+ dev: true
+
/@babel/runtime@7.23.5:
resolution: {integrity: sha512-NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.14.0
+ /@babel/template@7.23.9:
+ resolution: {integrity: sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.23.5
+ '@babel/parser': 7.23.9
+ '@babel/types': 7.23.9
+ dev: true
+
+ /@babel/traverse@7.23.2:
+ resolution: {integrity: sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.23.5
+ '@babel/generator': 7.23.6
+ '@babel/helper-environment-visitor': 7.22.20
+ '@babel/helper-function-name': 7.23.0
+ '@babel/helper-hoist-variables': 7.22.5
+ '@babel/helper-split-export-declaration': 7.22.6
+ '@babel/parser': 7.23.9
+ '@babel/types': 7.23.9
+ debug: 4.3.4
+ globals: 11.12.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@babel/types@7.17.0:
+ resolution: {integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-validator-identifier': 7.22.20
+ to-fast-properties: 2.0.0
+ dev: true
+
+ /@babel/types@7.23.9:
+ resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-string-parser': 7.23.4
+ '@babel/helper-validator-identifier': 7.22.20
+ to-fast-properties: 2.0.0
+ dev: true
+
/@emotion/is-prop-valid@0.8.8:
resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==}
requiresBuild: true
@@ -2355,6 +2495,26 @@ packages:
engines: {node: '>=12'}
dev: false
+ /@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.2.5):
+ resolution: {integrity: sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ==}
+ peerDependencies:
+ '@vue/compiler-sfc': 3.x
+ prettier: 2.x - 3.x
+ peerDependenciesMeta:
+ '@vue/compiler-sfc':
+ optional: true
+ dependencies:
+ '@babel/generator': 7.17.7
+ '@babel/parser': 7.23.9
+ '@babel/traverse': 7.23.2
+ '@babel/types': 7.17.0
+ javascript-natural-sort: 0.7.1
+ lodash: 4.17.21
+ prettier: 3.2.5
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@twilio/voice-errors@1.4.0:
resolution: {integrity: sha512-7BCg9MPz+KQ0JJ6Rl5W3Zw3E+i3Tt77VZw3/2i3Z+IPZITmCOQLu1nKx/0Nlj505Xtfr7eY9Mcern5PfIoBW0w==}
dev: false
@@ -2371,6 +2531,10 @@ packages:
rtcpeerconnection-shim: 1.2.8
dev: false
+ /@types/json-schema@7.0.15:
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+ dev: true
+
/@types/json5@0.0.29:
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
dev: true
@@ -2403,6 +2567,39 @@ packages:
/@types/scheduler@0.16.8:
resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==}
+ /@types/semver@7.5.7:
+ resolution: {integrity: sha512-/wdoPq1QqkSj9/QOeKkFquEuPzQbHTWAMPH/PaUMB+JuR31lXhlWXRZ52IpfDYVlDOUBvX09uBrPwxGT1hjNBg==}
+ dev: true
+
+ /@typescript-eslint/eslint-plugin@7.0.2(@typescript-eslint/parser@7.0.2)(eslint@8.55.0)(typescript@5.3.3):
+ resolution: {integrity: sha512-/XtVZJtbaphtdrWjr+CJclaCVGPtOdBpFEnvtNf/jRV0IiEemRrL0qABex/nEt8isYcnFacm3nPHYQwL+Wb7qg==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^7.0.0
+ eslint: ^8.56.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@eslint-community/regexpp': 4.10.0
+ '@typescript-eslint/parser': 7.0.2(eslint@8.55.0)(typescript@5.3.3)
+ '@typescript-eslint/scope-manager': 7.0.2
+ '@typescript-eslint/type-utils': 7.0.2(eslint@8.55.0)(typescript@5.3.3)
+ '@typescript-eslint/utils': 7.0.2(eslint@8.55.0)(typescript@5.3.3)
+ '@typescript-eslint/visitor-keys': 7.0.2
+ debug: 4.3.4
+ eslint: 8.55.0
+ graphemer: 1.4.0
+ ignore: 5.3.0
+ natural-compare: 1.4.0
+ semver: 7.5.4
+ ts-api-utils: 1.0.3(typescript@5.3.3)
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.3):
resolution: {integrity: sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -2424,6 +2621,27 @@ packages:
- supports-color
dev: true
+ /@typescript-eslint/parser@7.0.2(eslint@8.55.0)(typescript@5.3.3):
+ resolution: {integrity: sha512-GdwfDglCxSmU+QTS9vhz2Sop46ebNCXpPPvsByK7hu0rFGRHL+AusKQJ7SoN+LbLh6APFpQwHKmDSwN35Z700Q==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^8.56.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/scope-manager': 7.0.2
+ '@typescript-eslint/types': 7.0.2
+ '@typescript-eslint/typescript-estree': 7.0.2(typescript@5.3.3)
+ '@typescript-eslint/visitor-keys': 7.0.2
+ debug: 4.3.4
+ eslint: 8.55.0
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/scope-manager@6.13.2:
resolution: {integrity: sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -2432,11 +2650,44 @@ packages:
'@typescript-eslint/visitor-keys': 6.13.2
dev: true
+ /@typescript-eslint/scope-manager@7.0.2:
+ resolution: {integrity: sha512-l6sa2jF3h+qgN2qUMjVR3uCNGjWw4ahGfzIYsCtFrQJCjhbrDPdiihYT8FnnqFwsWX+20hK592yX9I2rxKTP4g==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dependencies:
+ '@typescript-eslint/types': 7.0.2
+ '@typescript-eslint/visitor-keys': 7.0.2
+ dev: true
+
+ /@typescript-eslint/type-utils@7.0.2(eslint@8.55.0)(typescript@5.3.3):
+ resolution: {integrity: sha512-IKKDcFsKAYlk8Rs4wiFfEwJTQlHcdn8CLwLaxwd6zb8HNiMcQIFX9sWax2k4Cjj7l7mGS5N1zl7RCHOVwHq2VQ==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^8.56.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/typescript-estree': 7.0.2(typescript@5.3.3)
+ '@typescript-eslint/utils': 7.0.2(eslint@8.55.0)(typescript@5.3.3)
+ debug: 4.3.4
+ eslint: 8.55.0
+ ts-api-utils: 1.0.3(typescript@5.3.3)
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/@typescript-eslint/types@6.13.2:
resolution: {integrity: sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==}
engines: {node: ^16.0.0 || >=18.0.0}
dev: true
+ /@typescript-eslint/types@7.0.2:
+ resolution: {integrity: sha512-ZzcCQHj4JaXFjdOql6adYV4B/oFOFjPOC9XYwCaZFRvqN8Llfvv4gSxrkQkd2u4Ci62i2c6W6gkDwQJDaRc4nA==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dev: true
+
/@typescript-eslint/typescript-estree@6.13.2(typescript@5.3.3):
resolution: {integrity: sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -2458,6 +2709,47 @@ packages:
- supports-color
dev: true
+ /@typescript-eslint/typescript-estree@7.0.2(typescript@5.3.3):
+ resolution: {integrity: sha512-3AMc8khTcELFWcKcPc0xiLviEvvfzATpdPj/DXuOGIdQIIFybf4DMT1vKRbuAEOFMwhWt7NFLXRkbjsvKZQyvw==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/types': 7.0.2
+ '@typescript-eslint/visitor-keys': 7.0.2
+ debug: 4.3.4
+ globby: 11.1.0
+ is-glob: 4.0.3
+ minimatch: 9.0.3
+ semver: 7.5.4
+ ts-api-utils: 1.0.3(typescript@5.3.3)
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/utils@7.0.2(eslint@8.55.0)(typescript@5.3.3):
+ resolution: {integrity: sha512-PZPIONBIB/X684bhT1XlrkjNZJIEevwkKDsdwfiu1WeqBxYEEdIgVDgm8/bbKHVu+6YOpeRqcfImTdImx/4Bsw==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^8.56.0
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0)
+ '@types/json-schema': 7.0.15
+ '@types/semver': 7.5.7
+ '@typescript-eslint/scope-manager': 7.0.2
+ '@typescript-eslint/types': 7.0.2
+ '@typescript-eslint/typescript-estree': 7.0.2(typescript@5.3.3)
+ eslint: 8.55.0
+ semver: 7.5.4
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+ dev: true
+
/@typescript-eslint/visitor-keys@6.13.2:
resolution: {integrity: sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==}
engines: {node: ^16.0.0 || >=18.0.0}
@@ -2466,6 +2758,14 @@ packages:
eslint-visitor-keys: 3.4.3
dev: true
+ /@typescript-eslint/visitor-keys@7.0.2:
+ resolution: {integrity: sha512-8Y+YiBmqPighbm5xA2k4wKTxRzx9EkBu7Rlw+WHqMvRJ3RPz/BMBO9b2ru0LUNmXg120PHUXD5+SWFy2R8DqlQ==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dependencies:
+ '@typescript-eslint/types': 7.0.2
+ eslint-visitor-keys: 3.4.3
+ dev: true
+
/@ungap/structured-clone@1.2.0:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: true
@@ -2512,6 +2812,13 @@ packages:
engines: {node: '>=8'}
dev: true
+ /ansi-styles@3.2.1:
+ resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
+ engines: {node: '>=4'}
+ dependencies:
+ color-convert: 1.9.3
+ dev: true
+
/ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
@@ -2698,6 +3005,12 @@ packages:
balanced-match: 1.0.2
concat-map: 0.0.1
+ /brace-expansion@2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+ dependencies:
+ balanced-match: 1.0.2
+ dev: true
+
/braces@3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
@@ -2753,6 +3066,15 @@ packages:
crc-32: 1.2.2
dev: false
+ /chalk@2.4.2:
+ resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
+ engines: {node: '>=4'}
+ dependencies:
+ ansi-styles: 3.2.1
+ escape-string-regexp: 1.0.5
+ supports-color: 5.5.0
+ dev: true
+
/chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -2810,6 +3132,12 @@ packages:
engines: {node: '>=0.8'}
dev: false
+ /color-convert@1.9.3:
+ resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
+ dependencies:
+ color-name: 1.1.3
+ dev: true
+
/color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -2817,6 +3145,10 @@ packages:
color-name: 1.1.4
dev: true
+ /color-name@1.1.3:
+ resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
+ dev: true
+
/color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
@@ -3079,6 +3411,11 @@ packages:
engines: {node: '>=6'}
dev: true
+ /escape-string-regexp@1.0.5:
+ resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
+ engines: {node: '>=0.8.0'}
+ dev: true
+
/escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
@@ -3099,7 +3436,7 @@ packages:
eslint: 8.55.0
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.0)(eslint@8.55.0)
- eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0)
+ eslint-plugin-import: 2.29.0(@typescript-eslint/parser@7.0.2)(eslint@8.55.0)
eslint-plugin-jsx-a11y: 6.8.0(eslint@8.55.0)
eslint-plugin-react: 7.33.2(eslint@8.55.0)
eslint-plugin-react-hooks: 4.6.0(eslint@8.55.0)
@@ -3109,6 +3446,15 @@ packages:
- supports-color
dev: true
+ /eslint-config-prettier@9.1.0(eslint@8.55.0):
+ resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
+ hasBin: true
+ peerDependencies:
+ eslint: '>=7.0.0'
+ dependencies:
+ eslint: 8.55.0
+ dev: true
+
/eslint-import-resolver-node@0.3.9:
resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==}
dependencies:
@@ -3130,7 +3476,7 @@ packages:
enhanced-resolve: 5.15.0
eslint: 8.55.0
eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0)
- eslint-plugin-import: 2.29.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0)
+ eslint-plugin-import: 2.29.0(@typescript-eslint/parser@7.0.2)(eslint@8.55.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.2
is-core-module: 2.13.1
@@ -3172,7 +3518,36 @@ packages:
- supports-color
dev: true
- /eslint-plugin-import@2.29.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0):
+ /eslint-module-utils@2.8.0(@typescript-eslint/parser@7.0.2)(eslint-import-resolver-node@0.3.9)(eslint@8.55.0):
+ resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
+ engines: {node: '>=4'}
+ peerDependencies:
+ '@typescript-eslint/parser': '*'
+ eslint: '*'
+ eslint-import-resolver-node: '*'
+ eslint-import-resolver-typescript: '*'
+ eslint-import-resolver-webpack: '*'
+ peerDependenciesMeta:
+ '@typescript-eslint/parser':
+ optional: true
+ eslint:
+ optional: true
+ eslint-import-resolver-node:
+ optional: true
+ eslint-import-resolver-typescript:
+ optional: true
+ eslint-import-resolver-webpack:
+ optional: true
+ dependencies:
+ '@typescript-eslint/parser': 7.0.2(eslint@8.55.0)(typescript@5.3.3)
+ debug: 3.2.7
+ eslint: 8.55.0
+ eslint-import-resolver-node: 0.3.9
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /eslint-plugin-import@2.29.0(@typescript-eslint/parser@7.0.2)(eslint@8.55.0):
resolution: {integrity: sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==}
engines: {node: '>=4'}
peerDependencies:
@@ -3182,7 +3557,7 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
- '@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.3)
+ '@typescript-eslint/parser': 7.0.2(eslint@8.55.0)(typescript@5.3.3)
array-includes: 3.1.7
array.prototype.findlastindex: 1.2.3
array.prototype.flat: 1.3.2
@@ -3191,7 +3566,7 @@ packages:
doctrine: 2.1.0
eslint: 8.55.0
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.13.2)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.55.0)
+ eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.0.2)(eslint-import-resolver-node@0.3.9)(eslint@8.55.0)
hasown: 2.0.0
is-core-module: 2.13.1
is-glob: 4.0.3
@@ -3591,6 +3966,11 @@ packages:
path-is-absolute: 1.0.1
dev: true
+ /globals@11.12.0:
+ resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
+ engines: {node: '>=4'}
+ dev: true
+
/globals@13.23.0:
resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==}
engines: {node: '>=8'}
@@ -3641,6 +4021,11 @@ packages:
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
dev: true
+ /has-flag@3.0.0:
+ resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
+ engines: {node: '>=4'}
+ dev: true
+
/has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -3910,6 +4295,10 @@ packages:
set-function-name: 2.0.1
dev: true
+ /javascript-natural-sort@0.7.1:
+ resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
+ dev: true
+
/jiti@1.21.0:
resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==}
hasBin: true
@@ -3928,6 +4317,12 @@ packages:
argparse: 2.0.1
dev: true
+ /jsesc@2.5.2:
+ resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
+ engines: {node: '>=4'}
+ hasBin: true
+ dev: true
+
/json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
dev: true
@@ -4069,7 +4464,6 @@ packages:
/lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
- dev: false
/loglevel@1.6.7:
resolution: {integrity: sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==}
@@ -4145,6 +4539,13 @@ packages:
dependencies:
brace-expansion: 1.1.11
+ /minimatch@9.0.3:
+ resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ dependencies:
+ brace-expansion: 2.0.1
+ dev: true
+
/minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: true
@@ -4845,6 +5246,11 @@ packages:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
+ /source-map@0.5.7:
+ resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
/ssf@0.11.2:
resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
engines: {node: '>=0.8'}
@@ -4954,6 +5360,13 @@ packages:
pirates: 4.0.6
ts-interface-checker: 0.1.13
+ /supports-color@5.5.0:
+ resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
+ engines: {node: '>=4'}
+ dependencies:
+ has-flag: 3.0.0
+ dev: true
+
/supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
@@ -4979,16 +5392,16 @@ packages:
'@babel/runtime': 7.23.5
dev: false
- /tailwindcss-animate@1.0.7(tailwindcss@3.3.6):
+ /tailwindcss-animate@1.0.7(tailwindcss@3.4.1):
resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
peerDependencies:
tailwindcss: '>=3.0.0 || insiders'
dependencies:
- tailwindcss: 3.3.6
+ tailwindcss: 3.4.1
dev: false
- /tailwindcss@3.3.6:
- resolution: {integrity: sha512-AKjF7qbbLvLaPieoKeTjG1+FyNZT6KaJMJPFeQyLfIp7l82ggH1fbHJSsYIvnbTFQOlkh+gBYpyby5GT1LIdLw==}
+ /tailwindcss@3.4.1:
+ resolution: {integrity: sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==}
engines: {node: '>=14.0.0'}
hasBin: true
dependencies:
@@ -5037,6 +5450,11 @@ packages:
dependencies:
any-promise: 1.3.0
+ /to-fast-properties@2.0.0:
+ resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
+ engines: {node: '>=4'}
+ dev: true
+
/to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
diff --git a/postcss.config.js b/postcss.config.js
index 33ad091..12a703d 100644
--- a/postcss.config.js
+++ b/postcss.config.js
@@ -3,4 +3,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
-}
+};
diff --git a/src/app/api/README.md b/src/app/api/README.md
new file mode 100644
index 0000000..32ac90a
--- /dev/null
+++ b/src/app/api/README.md
@@ -0,0 +1,3 @@
+# Resources
+
+Connie supports the following resources:
diff --git a/src/app/api/audit-log/calls/route.ts b/src/app/api/audit-log/calls/route.ts
index bb7624a..c211177 100644
--- a/src/app/api/audit-log/calls/route.ts
+++ b/src/app/api/audit-log/calls/route.ts
@@ -1,44 +1,50 @@
+import { NextResponse } from 'next/server';
+import { CallInstance } from 'twilio/lib/rest/api/v2010/account/call';
-import {NextResponse, NextRequest} from 'next/server';
+import twilioClient from '@/lib/server/comms/twilioClient';
import { formatDate } from '@/lib/utils';
+type Call = {
+ id: string;
+ direction: string;
+ from: string | null;
+ to: string | null;
+ timestamp: string;
+};
-interface Calls {
- id: string;
- direction: string;
- from: string | null;
- to: string | null;
- timestamp: string;
-}
+const DEFAULT_LIMIT = 20;
-export async function GET(
- request: NextRequest,
- res: NextResponse
-) {
- const accountSid = process.env.TWILIO_ACCOUNT_SID;
- const authToken = process.env.TWILIO_AUTH_TOKEN;
- const client = require('twilio')(accountSid, authToken);
- try{
- const calls: any[] = await client.calls.list({limit:20});
+/**
+ * Handles GET requests to `/api/audit-log/calls`
+ *
+ * This function fetches the call logs from Twilio and returns them in a
+ * format that can be consumed by the frontend.
+ */
+export async function GET() {
+ // TODO: Add limit and page query parameters to support pagination
+ try {
+ const calls: CallInstance[] = await twilioClient.calls.list({
+ limit: DEFAULT_LIMIT,
+ });
//if want to display all calls, can delete the limit parameter
- const formattedCalls: Calls[] = calls.map(call => {
- const date = new Date(call.dateCreated);
- const formattedDate = formatDate(date, 'YYYY-MM-DD HH:mm')
- return {
- id: call.sid,
- from: call.from,
- to: call.to,
- direction: call.direction,
- timestamp: formattedDate // Use the formatted date string
- };
+ const formattedCalls: Call[] = calls.map((call) => {
+ const date = new Date(call.dateCreated);
+ const formattedDate = formatDate(date, 'YYYY-MM-DD HH:mm');
+ return {
+ id: call.sid,
+ from: call.from,
+ to: call.to,
+ direction: call.direction,
+ timestamp: formattedDate,
+ };
});
- return NextResponse.json(formattedCalls)
-
- } catch (error) {
- console.error('Error fetching call logs', error);
- }
-
+ return NextResponse.json(formattedCalls);
+ } catch (error) {
+ return NextResponse.json(
+ { message: 'Error fetching call logs' },
+ { status: 500 },
+ );
+ }
}
-export const dynamic = 'force-dynamic'
-
+export const dynamic = 'force-dynamic';
diff --git a/src/app/api/audit-log/message/route.ts b/src/app/api/audit-log/message/route.ts
index 18eae20..31768c1 100644
--- a/src/app/api/audit-log/message/route.ts
+++ b/src/app/api/audit-log/message/route.ts
@@ -1,46 +1,51 @@
+import { NextResponse } from 'next/server';
+import { MessageInstance } from 'twilio/lib/rest/api/v2010/account/message';
-import {NextResponse, NextRequest} from 'next/server';
+import twilioClient from '@/lib/server/comms/twilioClient';
import { formatDate } from '@/lib/utils';
-interface Message {
- id: string;
- from: string;
- to: string;
- direction: string;
- timestamp: string;
-}
+type Message = {
+ id: string;
+ from: string;
+ to: string;
+ direction: string;
+ timestamp: string;
+};
+
+const DEFAULT_LIMIT = 20;
-export async function GET(
- request: NextRequest,
- res: NextResponse
-) {
- const accountSid = process.env.TWILIO_ACCOUNT_SID;
- const authToken = process.env.TWILIO_AUTH_TOKEN;
- const client = require('twilio')(accountSid, authToken);
- try{
- const messages: any[] = await client.messages.list({limit:20});
+/**
+ * Handles GET requests to `/api/audit-log/messages`
+ *
+ * This function fetches the message logs from Twilio and returns them in a
+ * format that can be consumed by the frontend.
+ */
+export async function GET() {
+ // TODO: Add limit and page query parameters to support pagination
+ try {
+ const messages: MessageInstance[] = await twilioClient.messages.list({
+ limit: DEFAULT_LIMIT,
+ });
//if want to display all calls, can delete the limit parameter
- const formattedMessages: Message[] = messages.map(message => {
- const date = new Date(message.dateCreated);
- const formattedDate = formatDate(date, 'YYYY-MM-DD HH:mm')
- return{
- id: message.sid,
- direction: message.direction,
- from: message.from,
- to: message.to,
- timestamp: formattedDate
- }
-
-
- })
-
- // console.log(formattedMessages)
- return NextResponse.json(formattedMessages)
-
- } catch (error) {
- console.error('Error fetching message logs', error);
- }
-
+ const formattedMessages: Message[] = messages.map((message) => {
+ const date = new Date(message.dateCreated);
+ const formattedDate = formatDate(date, 'YYYY-MM-DD HH:mm');
+ return {
+ id: message.sid,
+ direction: message.direction,
+ from: message.from,
+ to: message.to,
+ timestamp: formattedDate,
+ };
+ });
+
+ return NextResponse.json(formattedMessages);
+ } catch (error) {
+ return NextResponse.json(
+ { message: 'Error fetching call logs' },
+ { status: 500 },
+ );
+ }
}
-export const dynamic = 'force-dynamic'
\ No newline at end of file
+export const dynamic = 'force-dynamic';
diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts
index 04838fd..ce8773c 100644
--- a/src/app/api/auth/[...nextauth]/route.ts
+++ b/src/app/api/auth/[...nextauth]/route.ts
@@ -1,5 +1,9 @@
+/**
+ * Handler for all auth routes.
+ */
import NextAuth from 'next-auth';
+
import { authOptions } from '@/lib/auth';
const handler = NextAuth(authOptions);
-export { handler as GET, handler as POST };
\ No newline at end of file
+export { handler as GET, handler as POST };
diff --git a/src/app/api/crm_example/route.ts b/src/app/api/crm_example/route.ts
index 411c3cd..05f25e8 100644
--- a/src/app/api/crm_example/route.ts
+++ b/src/app/api/crm_example/route.ts
@@ -1,48 +1,66 @@
-import { AirtableCRMProvider, s3KeyForAirtableToken, s3KeyForAirtableBase, s3KeyForAirtableTable } from "../../../lib/crm/airtable";
-import { authOptions } from "@/lib/auth";
-import { getObjectString } from "@/lib/aws/s3";
-import { getServerSession } from "next-auth/next";
-import { NextRequest} from "next/server";
-// ENV variables for now, pending CRM configuration feature
+import { getServerSession } from 'next-auth/next';
+import { NextRequest, NextResponse } from 'next/server';
+import { authOptions } from '@/lib/auth';
+import { getObjectString } from '@/lib/aws/s3';
+import {
+ AirtableCRMProvider,
+ s3KeyForAirtableBase,
+ s3KeyForAirtableTable,
+ s3KeyForAirtableToken,
+} from '@/lib/crm/airtable';
/**
- * GET /api/crm_example?phone={phone_number_string}
- *
+ * Handles GET requests to /api/crm_example?phone={phone_number_string}
+ *
* @param request - The incoming request object.
* @returns A JSON response with the client info.
-*/
+ */
export async function GET(request: NextRequest) {
- const session = await getServerSession(authOptions) as any;
+ const session = await getServerSession(authOptions);
if (!session) {
- return new Response('Unauthorized', { status: 401 });
+ return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
}
const oktaId = session.oktaId;
-
- const [airtable_token, airtable_base_id, airtable_table_id] = await Promise.all([
- getObjectString(s3KeyForAirtableToken(oktaId)),
- getObjectString(s3KeyForAirtableBase(oktaId)),
- getObjectString(s3KeyForAirtableTable(oktaId))
- ]);
- console.log(airtable_token, airtable_base_id, airtable_table_id)
+ const [airtable_token, airtable_base_id, airtable_table_id] =
+ await Promise.all([
+ getObjectString(s3KeyForAirtableToken(oktaId)),
+ getObjectString(s3KeyForAirtableBase(oktaId)),
+ getObjectString(s3KeyForAirtableTable(oktaId)),
+ ]);
+
+ console.log(airtable_token, airtable_base_id, airtable_table_id);
const provider = new AirtableCRMProvider(airtable_base_id, airtable_token);
const table = provider.getTable(airtable_table_id);
const url = new URL(request.url);
- // const queryParams = new URLSearchParams(url.search);
- // const phone: string = queryParams.get("phone") || "1234567890";
-
- // const clientInfo = await table.getClientByPhone(phone);
+ const queryParams = new URLSearchParams(url.search);
+ if (queryParams.has('phone')) {
+ const phone: string = queryParams.get('phone') || '1234567890';
+ // TODO: Handle phone number validation
+ const clientInfo = await table.getClientByPhone(phone);
+ if (!clientInfo) {
+ return NextResponse.json(
+ { message: 'Client not found' },
+ { status: 404 },
+ );
+ }
+ return NextResponse.json(clientInfo, {
+ headers: {
+ 'content-type': 'application/json',
+ },
+ });
+ }
const allClients = await table.getAllRows();
- return new Response(JSON.stringify(allClients), {
+ return NextResponse.json(allClients, {
headers: {
- "content-type": "application/json",
+ 'content-type': 'application/json',
},
});
-}
\ No newline at end of file
+}
diff --git a/src/app/api/crmconfig/airtable/route.ts b/src/app/api/crmconfig/airtable/route.ts
index 69d63e6..25098d0 100644
--- a/src/app/api/crmconfig/airtable/route.ts
+++ b/src/app/api/crmconfig/airtable/route.ts
@@ -1,9 +1,13 @@
-import { NextApiRequest, NextApiResponse } from 'next';
-import { getServerSession } from "next-auth/next";
-import { putObject, getObjectString } from '../../../../lib/aws/s3';
+import { getServerSession } from 'next-auth/next';
+import { NextRequest } from 'next/server';
+
import { authOptions } from '@/lib/auth';
-import { NextRequest, NextResponse } from 'next/server';
-import { s3KeyForAirtableBase, s3KeyForAirtableTable, s3KeyForAirtableToken } from '@/lib/crm/airtable';
+import { putObject } from '@/lib/aws/s3';
+import {
+ s3KeyForAirtableBase,
+ s3KeyForAirtableTable,
+ s3KeyForAirtableToken,
+} from '@/lib/crm/airtable';
type AirtableTokenData = {
token: string;
@@ -12,35 +16,34 @@ type AirtableTokenData = {
};
export async function POST(req: NextRequest) {
- const session = await getServerSession(authOptions) as any;
-
-
- const res = NextResponse.next();
+ const session = await getServerSession(authOptions);
+
if (!session) {
return new Response('Unauthorized', { status: 401 });
}
- const oktaId = session.oktaId;
- const body = await req.json() as AirtableTokenData;
+ const oktaId = session.oktaId;
+ const body = (await req.json()) as AirtableTokenData;
const token = body.token;
const baseId = body.baseId;
const tableId = body.tableId;
-
+
try {
const tokenKey = s3KeyForAirtableToken(oktaId);
const baseIdKey = s3KeyForAirtableBase(oktaId);
const tableIdKey = s3KeyForAirtableTable(oktaId);
-
+
await Promise.all([
putObject(tokenKey, token),
putObject(baseIdKey, baseId),
- putObject(tableIdKey, tableId)
+ putObject(tableIdKey, tableId),
]);
-
return new Response('Success', { status: 200 });
} catch (error) {
console.error(error);
- return new Response('Put in S3 unsuccessful: ' + error, { status: 500 });
+ return new Response('Put in S3 unsuccessful: ' + error, {
+ status: 500,
+ });
}
-};
\ No newline at end of file
+}
diff --git a/src/app/api/notes/route.ts b/src/app/api/notes/route.ts
index 29b62c8..174934b 100644
--- a/src/app/api/notes/route.ts
+++ b/src/app/api/notes/route.ts
@@ -1,141 +1,147 @@
-import { NextRequest } from "next/server";
+import { NextRequest, NextResponse } from 'next/server';
// Test Data
const test_notes = [
{
- clientId: "12345",
- id: "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
- author: "William Smith",
- callDate: "2011-10-05T14:48:00.000Z",
- callDuration: "13 min 20 sec",
- dateCreated: "2011-10-05T14:48:00.000Z",
- dateUpdated: "2011-10-05T14:48:00.000Z",
+ clientId: '12345',
+ id: '6c84fb90-12c4-11e1-840d-7b25c5ee775a',
+ author: 'William Smith',
+ callDate: '2011-10-05T14:48:00.000Z',
+ callDuration: '13 min 20 sec',
+ dateCreated: '2011-10-05T14:48:00.000Z',
+ dateUpdated: '2011-10-05T14:48:00.000Z',
content:
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
- labels: ["meeting", "work", "important"],
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ labels: ['meeting', 'work', 'important'],
},
{
- clientId: "12345",
- id: "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
- author: "William Smith",
- callDate: "2011-10-05T14:48:00.000Z",
- callDuration: "13 min 20 sec",
- dateCreated: "2012-10-05T14:48:00.000Z",
- dateUpdated: "2011-10-05T14:48:00.000Z",
- content: "moved to germany, mobility issues",
- labels: ["meeting", "work", "important"],
+ clientId: '12345',
+ id: '6c84fb90-12c4-11e1-840d-7b25c5ee775a',
+ author: 'William Smith',
+ callDate: '2011-10-05T14:48:00.000Z',
+ callDuration: '13 min 20 sec',
+ dateCreated: '2012-10-05T14:48:00.000Z',
+ dateUpdated: '2011-10-05T14:48:00.000Z',
+ content: 'moved to germany, mobility issues',
+ labels: ['meeting', 'work', 'important'],
},
{
- clientId: "12345",
- id: "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
- author: "William Smith",
- callDate: "2011-10-05T14:48:00.000Z",
- callDuration: "13 min 20 sec",
- dateCreated: "2011-10-05T14:48:00.000Z",
- dateUpdated: "2011-10-05T14:48:00.000Z",
- content: "moved to germany, mobility issues",
- labels: ["meeting", "work", "important"],
+ clientId: '12345',
+ id: '6c84fb90-12c4-11e1-840d-7b25c5ee775a',
+ author: 'William Smith',
+ callDate: '2011-10-05T14:48:00.000Z',
+ callDuration: '13 min 20 sec',
+ dateCreated: '2011-10-05T14:48:00.000Z',
+ dateUpdated: '2011-10-05T14:48:00.000Z',
+ content: 'moved to germany, mobility issues',
+ labels: ['meeting', 'work', 'important'],
},
{
- clientId: "12345",
- id: "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
- author: "William Smith",
- callDate: "2011-10-05T14:48:00.000Z",
- callDuration: "13 min 20 sec",
- dateCreated: "2011-10-05T14:48:00.000Z",
- dateUpdated: "2011-10-05T14:48:00.000Z",
+ clientId: '12345',
+ id: '6c84fb90-12c4-11e1-840d-7b25c5ee775a',
+ author: 'William Smith',
+ callDate: '2011-10-05T14:48:00.000Z',
+ callDuration: '13 min 20 sec',
+ dateCreated: '2011-10-05T14:48:00.000Z',
+ dateUpdated: '2011-10-05T14:48:00.000Z',
content:
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
- labels: ["meeting", "work", "important"],
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+ labels: ['meeting', 'work', 'important'],
},
{
- clientId: "4567",
- id: "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
- author: "William Smith",
- callDate: "2011-10-05T14:48:00.000Z",
- callDuration: "13 min 20 sec",
- dateCreated: "2012-10-05T14:48:00.000Z",
- dateUpdated: "2011-10-05T14:48:00.000Z",
- content: "this is a test note",
- labels: ["meeting", "work", "important"],
+ clientId: '4567',
+ id: '6c84fb90-12c4-11e1-840d-7b25c5ee775a',
+ author: 'William Smith',
+ callDate: '2011-10-05T14:48:00.000Z',
+ callDuration: '13 min 20 sec',
+ dateCreated: '2012-10-05T14:48:00.000Z',
+ dateUpdated: '2011-10-05T14:48:00.000Z',
+ content: 'this is a test note',
+ labels: ['meeting', 'work', 'important'],
},
{
- clientId: "4567",
- id: "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
- author: "William Smith",
- callDate: "2011-10-05T14:48:00.000Z",
- callDuration: "13 min 20 sec",
- dateCreated: "2011-10-05T14:48:00.000Z",
- dateUpdated: "2011-10-05T14:48:00.000Z",
- content: "moved to germany, mobility issues",
- labels: ["meeting", "work", "important"],
+ clientId: '4567',
+ id: '6c84fb90-12c4-11e1-840d-7b25c5ee775a',
+ author: 'William Smith',
+ callDate: '2011-10-05T14:48:00.000Z',
+ callDuration: '13 min 20 sec',
+ dateCreated: '2011-10-05T14:48:00.000Z',
+ dateUpdated: '2011-10-05T14:48:00.000Z',
+ content: 'moved to germany, mobility issues',
+ labels: ['meeting', 'work', 'important'],
},
{
- clientId: "12345",
- id: "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
- author: "William Smith",
- callDate: "2011-10-05T14:48:00.000Z",
- callDuration: "13 min 20 sec",
- dateCreated: "2019-10-05T14:48:00.000Z",
- dateUpdated: "2011-10-05T14:48:00.000Z",
- content: "moved to germany, mobility issues",
- labels: ["meeting", "work", "important"],
+ clientId: '12345',
+ id: '6c84fb90-12c4-11e1-840d-7b25c5ee775a',
+ author: 'William Smith',
+ callDate: '2011-10-05T14:48:00.000Z',
+ callDuration: '13 min 20 sec',
+ dateCreated: '2019-10-05T14:48:00.000Z',
+ dateUpdated: '2011-10-05T14:48:00.000Z',
+ content: 'moved to germany, mobility issues',
+ labels: ['meeting', 'work', 'important'],
},
{
- clientId: "12345",
- id: "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
- author: "William Smith",
- callDate: "2011-10-05T14:48:00.000Z",
- callDuration: "13 min 20 sec",
- dateCreated: "2011-10-05T14:48:00.000Z",
- dateUpdated: "2011-10-05T14:48:00.000Z",
- content: "moved to germany, mobility issues",
- labels: ["important"],
+ clientId: '12345',
+ id: '6c84fb90-12c4-11e1-840d-7b25c5ee775a',
+ author: 'William Smith',
+ callDate: '2011-10-05T14:48:00.000Z',
+ callDuration: '13 min 20 sec',
+ dateCreated: '2011-10-05T14:48:00.000Z',
+ dateUpdated: '2011-10-05T14:48:00.000Z',
+ content: 'moved to germany, mobility issues',
+ labels: ['important'],
},
{
- clientId: "4567",
- id: "6c84fb90-12c4-11e1-840d-7b25c5ee775a",
- author: "William Smith",
- callDate: "2011-10-05T14:48:00.000Z",
- callDuration: "13 min 20 sec",
- dateCreated: "2011-10-05T14:48:00.000Z",
- dateUpdated: "2011-10-05T14:48:00.000Z",
- content: "moved to germany, mobility issues",
- labels: ["important"],
+ clientId: '4567',
+ id: '6c84fb90-12c4-11e1-840d-7b25c5ee775a',
+ author: 'William Smith',
+ callDate: '2011-10-05T14:48:00.000Z',
+ callDuration: '13 min 20 sec',
+ dateCreated: '2011-10-05T14:48:00.000Z',
+ dateUpdated: '2011-10-05T14:48:00.000Z',
+ content: 'moved to germany, mobility issues',
+ labels: ['important'],
},
];
export async function GET(req: NextRequest) {
const searchParams = req.nextUrl.searchParams;
- const client: string = searchParams.get("clientId") ?? "";
+ const clientId: string = searchParams.get('clientId') ?? '';
// TODO implement fetch from external data source api used for notes
- return Response.json(test_notes.filter((n) => n.clientId === client));
+ return Response.json(test_notes.filter((note) => note.clientId === clientId));
}
-export async function POST(req: NextRequest) {
- const searchParams = req.nextUrl.searchParams;
- const client: string = searchParams.get("clientId") ?? "";
+export async function POST() {
+ // req: NextRequest
+ // const searchParams = req.nextUrl.searchParams;
+ // const client: string = searchParams.get('clientId') ?? '';
// TODO add the note to the data source
// const resp = await...
- const resp = { id: "1234" };
+ const resp = { id: '1234' };
return new Response(JSON.stringify(resp), {
status: 200,
- headers: { "Content-Type": "application/json" },
+ headers: { 'Content-Type': 'application/json' },
});
}
-export async function DELETE(req: NextRequest) {
- const searchParams = req.nextUrl.searchParams;
- const id: string = searchParams.get("id") ?? "";
+export async function DELETE() {
+ // req: NextRequest
+ // TODO: Implement delete note
+ // const searchParams = req.nextUrl.searchParams;
+ // const id: string = searchParams.get('id') ?? '';
// TODO delete from data source
// const resp = await...
- return new Response("Success", {
- status: 200,
- headers: { "Content-Type": "application/json" },
- });
+ return NextResponse.json(
+ { result: 'Success' },
+ {
+ status: 200,
+ headers: { 'Content-Type': 'application/json' },
+ },
+ );
}
diff --git a/src/app/api/token/route.ts b/src/app/api/token/route.ts
index c2132d6..87c5300 100644
--- a/src/app/api/token/route.ts
+++ b/src/app/api/token/route.ts
@@ -1,5 +1,5 @@
-import { type NextRequest } from "next/server";
-import AccessToken, { VoiceGrant } from "twilio/lib/jwt/AccessToken";
+import { type NextRequest } from 'next/server';
+import AccessToken, { VoiceGrant } from 'twilio/lib/jwt/AccessToken';
const account_sid = process.env.TWILIO_ACCOUNT_SID;
const application_sid = process.env.TWILIO_TWIML_APP_SID;
@@ -9,7 +9,7 @@ const api_secret = process.env.TWILIO_API_SECRET;
export async function GET(req: NextRequest) {
// Generate a random user name and store it
const searchParams = req.nextUrl.searchParams;
- const client: string = searchParams.get("client") ?? "";
+ const client: string = searchParams.get('client') ?? '';
// Create access token with credentials
const token = new AccessToken(account_sid!, api_key!, api_secret!, {
diff --git a/src/app/api/voice/route.ts b/src/app/api/voice/route.ts
index c03bd01..af0ebe1 100644
--- a/src/app/api/voice/route.ts
+++ b/src/app/api/voice/route.ts
@@ -1,5 +1,5 @@
-import { type NextRequest } from "next/server";
-import VoiceResponse from "twilio/lib/twiml/VoiceResponse";
+import { type NextRequest } from 'next/server';
+import VoiceResponse from 'twilio/lib/twiml/VoiceResponse';
export async function POST(req: NextRequest) {
const callerId = process.env.TWILIO_CALLER_ID;
@@ -16,35 +16,36 @@ export async function POST(req: NextRequest) {
// If the request to the /voice endpoint is TO your Twilio Number,
// then it is an incoming call towards your Twilio.Device.
if (bodyTo == callerId) {
+ // TODO: Implement
// Incoming call
resp.say("Please hold");
// Add call to task queue
resp.enqueue({ workflowSid: workflowSid });
// const dial = resp.dial({ callerId: bodyFrom });
// dial.client('atsarapk@uwaterloo.ca');
-
+
} else if (bodyTo) {
// Outgoing call
const dial = resp.dial({ callerId });
dial.number({}, bodyTo);
} else {
- resp.say("Thanks for calling!");
+ resp.say('Thanks for calling!');
}
return new Response(resp.toString(), {
status: 200,
headers: {
- "Content-Type": "text/xml",
+ 'Content-Type': 'text/xml',
},
});
} catch (error) {
- console.error("Error processing request:", error);
+ console.error('Error processing request:', error);
// Handle the error and return an appropriate response
- return new Response("Voice error", {
+ return new Response('Voice error', {
status: 500,
headers: {
- "Content-Type": "text/plain",
+ 'Content-Type': 'text/plain',
},
});
}
diff --git a/src/app/dashboard/OnboardingDialog.tsx b/src/app/dashboard/OnboardingDialog.tsx
new file mode 100644
index 0000000..9b33b00
--- /dev/null
+++ b/src/app/dashboard/OnboardingDialog.tsx
@@ -0,0 +1,137 @@
+'use client';
+
+import Image, { StaticImageData } from 'next/image';
+import React, { useState } from 'react';
+
+import connieLogo from '@/assets/connie-logo.png';
+import { Button } from '@/components/ui/button';
+import { Card } from '@/components/ui/card';
+import useOnboardingState from '@/lib/client/use-onboarding-state';
+import { cn } from '@/lib/utils';
+
+type OnboardingData = {
+ screenshot: StaticImageData;
+ title: string;
+ description: string;
+};
+
+const ONBOARDING_STEPS: OnboardingData[] = [
+ {
+ screenshot: connieLogo,
+ title: 'Call clients',
+ description:
+ 'Connie allows you to call clients directly from the dashboard. Click on the phone icon to start a call.',
+ },
+ {
+ screenshot: connieLogo,
+ title: 'Chat with Clients',
+ description: 'To chat with a client, simply search for them.',
+ },
+ {
+ screenshot: connieLogo,
+ title: 'See tasks',
+ description:
+ 'Miss a call? No problem. You can see all missed calls and tasks in the "Tasks" tab.',
+ },
+ {
+ screenshot: connieLogo,
+ title: 'Change Status',
+ description:
+ 'To receive incoming calls from clients, change your status to "Available" in the app toolbar.',
+ },
+];
+
+/**
+ * An onboarding dialog that appears when the user first logs in.
+ *
+ * This presents the user with a short guide on how to use the app.
+ *
+ * Note: This only appears if the user has not completed the onboarding process.
+ */
+export function OnboardingDialog() {
+ const { isOnboarded, setIsOnboarded } = useOnboardingState();
+ const [currentStep, setCurrentStep] = useState(0);
+
+ const advanceStep = () => {
+ if (currentStep === ONBOARDING_STEPS.length - 1) {
+ onFinish();
+ } else {
+ setCurrentStep(currentStep + 1);
+ }
+ };
+
+ /**
+ * Go back to the previous step, making sure we don't go below 0.
+ */
+ const goBack = () => {
+ setCurrentStep(Math.max(currentStep - 1, 0));
+ };
+
+ const onFinish = () => {
+ setIsOnboarded(true);
+ };
+
+ if (isOnboarded) {
+ return <>>;
+ }
+
+ const isPastCurrentStep = (index: number) => index <= currentStep;
+
+ const isDone = currentStep >= ONBOARDING_STEPS.length - 1;
+ const currentContent = ONBOARDING_STEPS[currentStep];
+
+ return (
+
+
+
+
+ Welcome to Connie.
+
+
+ We're so happy you're here. Get to know the app through a
+ few simple steps.
+
+
+
+
+
+ {ONBOARDING_STEPS.map(({ title }, index) => (
+
+ ))}
+
+
+
+
+
+
{currentContent.description}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/dashboard/clients/[client]/page.tsx b/src/app/dashboard/clients/[client]/page.tsx
index bfba1fc..971a104 100644
--- a/src/app/dashboard/clients/[client]/page.tsx
+++ b/src/app/dashboard/clients/[client]/page.tsx
@@ -1,7 +1,9 @@
-"use client";
-import { useState } from "react";
-import Notes from "@/components/notes";
-import { Button } from "@/components/ui/button";
+'use client';
+
+import { useState } from 'react';
+
+import Notes from '@/components/notes';
+import { Button } from '@/components/ui/button';
// TODO check if user exists
diff --git a/src/app/dashboard/clients/page.tsx b/src/app/dashboard/clients/page.tsx
index 6d03450..00c689e 100644
--- a/src/app/dashboard/clients/page.tsx
+++ b/src/app/dashboard/clients/page.tsx
@@ -1,7 +1,7 @@
-import { ClientTable } from "@/components/clientTable/ClientTable";
-import { columns } from "@/components/clientTable/columns";
-import { CRMEntry } from "@/lib/crm/types";
-import fetchClients from "@/lib/data/clients";
+import { ClientTable } from '@/components/clientTable/ClientTable';
+import { columns } from '@/components/clientTable/columns';
+import { CRMEntry } from '@/lib/crm/types';
+import fetchClients from '@/lib/data/clients';
export default async function ClientsPage() {
const data: CRMEntry[] = await fetchClients();
diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx
index ed3f94a..7ce7b14 100644
--- a/src/app/dashboard/layout.tsx
+++ b/src/app/dashboard/layout.tsx
@@ -1,47 +1,48 @@
-"use client";
+'use client';
+
import { useSession } from 'next-auth/react';
-import { redirect } from 'next/navigation'
+import { redirect } from 'next/navigation';
import React from 'react';
-import Appbar from "../../components/appbar";
-import Sidebar from "../../components/sidebar";
-import "../../styles/globals.css";
+import Appbar from '../../components/appbar';
+import Sidebar from '../../components/sidebar';
+import '../../styles/globals.css';
export default function Layout({
- children,
+ children,
}: {
- children: React.ReactNode;
+ children: React.ReactNode;
}) {
- const { data: session, status } = useSession();
- if (status === 'loading') {
- return Loading...;
- } else if (session) {
- const isProgramManager = true; // TODO
- let initials = "AA";
- if (session.user?.name) {
- const parts = session.user.name.split(' ');
- if (parts.length > 1) {
- initials = (parts[0][0] + parts[1][0]).toUpperCase();
- } else if (parts.length > 0) {
- initials = parts[0][0].toUpperCase();
- }
- }
- return (
-
- );
- } else {
- redirect('/login');
+ const { data: session, status } = useSession();
+ if (status === 'loading') {
+ return Loading...;
+ } else if (session) {
+ const isProgramManager = true; // TODO
+ let initials = "AA";
+ if (session.user?.name) {
+ const parts = session.user.name.split(' ');
+ if (parts.length > 1) {
+ initials = (parts[0][0] + parts[1][0]).toUpperCase();
+ } else if (parts.length > 0) {
+ initials = parts[0][0].toUpperCase();
+ }
}
+ return (
+
+ );
+ } else {
+ redirect('/login');
+ }
}
diff --git a/src/app/dashboard/Home.css b/src/app/dashboard/onboarding.css
similarity index 68%
rename from src/app/dashboard/Home.css
rename to src/app/dashboard/onboarding.css
index 6833940..244d69c 100644
--- a/src/app/dashboard/Home.css
+++ b/src/app/dashboard/onboarding.css
@@ -6,22 +6,22 @@
width: 100%; /* Adjust width as needed */
height: 45px; /* Adjust height as needed */
border-radius: 30px;
- border: 2px solid #E2E8F0; /* Adjust color as needed */
+ border: 2px solid #e2e8f0; /* Adjust color as needed */
transform: translate(-50%, -50%);
z-index: 0;
}
.step-oval.active::before {
- border-color: #FF9500; /* Adjust for active step */
+ border-color: #ff9500; /* Adjust for active step */
}
.step-oval.completed::before {
- border-color: #FF9500; /* Adjust for completed step */
+ border-color: #ff9500; /* Adjust for completed step */
}
.step-checkmark::after {
content: '\2714'; /* Unicode checkmark */
- color: #FF9500; /* Adjust checkmark color as needed */
+ color: #ff9500; /* Adjust checkmark color as needed */
font-size: 18px;
position: absolute;
top: 50%;
@@ -29,5 +29,3 @@
transform: translate(-50%, -50%);
z-index: 1;
}
-
-
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index 93febf3..f3c3548 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -1,103 +1,37 @@
'use client';
-import React, { useState } from 'react';
-import './Home.css';
-
-
-const steps = [
- "Dashboard Cards",
- "Notifications",
- "Communicate With Clients",
- "Change Status"
-];
-
-
+import React from 'react';
+
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+
+import { OnboardingDialog } from './OnboardingDialog';
+import './onboarding.css';
+
+/**
+ * The dashboard home page, showing an actionable summary for the user.
+ *
+ * Route: `/dashboard`
+ */
export default function DashboardHome() {
- const [currentPage, setCurrentPage] = useState(0);
- const [completedSteps, setCompletedSteps] = useState(new Array(steps.length).fill(false));
- // const tutorialCompleted = localStorage.getItem('tutorialCompleted') === 'true';
- // const [showPopup, setShowPopup] = useState(!tutorialCompleted);
- const [showPopup, setShowPopup] = useState(true);
- const [showFinishButton, setShowFinishButton] = useState(false);
-
-
- const goToNextPage = () => {
- const newCompletedSteps = [...completedSteps];
- newCompletedSteps[currentPage] = true;
- setCompletedSteps(newCompletedSteps);
- if (currentPage < steps.length - 1) {
- setCurrentPage(currentPage + 1);
- } else {
- setShowFinishButton(true);
- }
- };
-
-
- const finishTutorial = () => {
- // Set the last step as completed before closing
- const newCompletedSteps = [...completedSteps];
- newCompletedSteps[currentPage] = true;
- setCompletedSteps(newCompletedSteps);
- // localStorage.setItem('tutorialCompleted', 'true');
- setShowPopup(false);
- };
-
-
return (
-
- {showPopup && (
-
-
-
-
Welcome to Connie.
-
We’re so happy you’re here. Get to know your space through a few simple steps. Here are the main functions of your dashboard:
-
-
-
-
- {steps.map((step, index) => (
-
- ))}
-
-
-
-
- {steps.map((step, index) => (
-
- ))}
-
-
- {steps.map((content, index) => (
-
- {`Content for ${content}`}
-
- ))}
-
-
-
- {!showFinishButton ? (
-
- ) : (
-
- )}
-
-
- )}
+
+
+
+ Dashboard
+
+ Welcome to your dashboard. Here you can see a summary of your
+ clients and their status.
+
+
+
+
+
);
}
diff --git a/src/app/dashboard/settings/org/audit-log/page.tsx b/src/app/dashboard/settings/org/audit-log/page.tsx
index f6c503e..7a544e8 100644
--- a/src/app/dashboard/settings/org/audit-log/page.tsx
+++ b/src/app/dashboard/settings/org/audit-log/page.tsx
@@ -1,20 +1,17 @@
-import AuditTable from "@/components/audittable/AuditTable";
-import { Button } from "@/components/ui/button";
-
+import AuditTable from '@/components/audittable/AuditTable';
export default function AuditLog() {
-
- return (
-
+ return (
+
Audit Log
View all calls and SMS message your organization made via Connie.
-
+
-
- );
- }
\ No newline at end of file
+
+ );
+}
diff --git a/src/app/dashboard/settings/page.tsx b/src/app/dashboard/settings/page.tsx
index c62ab7a..f75d7fe 100644
--- a/src/app/dashboard/settings/page.tsx
+++ b/src/app/dashboard/settings/page.tsx
@@ -1,56 +1,60 @@
'use client';
import React from 'react';
+
import SingleForm from '@/components/ui/single-form';
const Page = () => {
- const AIRTABLE_TOKEN_STR = "Airtable Token";
- const AIRTABLE_BASE_ID_STR = "Airtable Base ID";
- const AIRTABLE_TABLE_ID_STR = "Airtable Table ID";
-
- const onSubmit = async (event: React.FormEvent
) => {
- event.preventDefault();
- const formData = new FormData(event.currentTarget);
- const airtableToken = formData.get(AIRTABLE_TOKEN_STR);
- const airtableBaseId = formData.get(AIRTABLE_BASE_ID_STR);
- const airtableTableId = formData.get(AIRTABLE_TABLE_ID_STR);
-
- try {
- const response = await fetch('../api/crmconfig/airtable', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ token: airtableToken,
- baseId: airtableBaseId,
- tableId: airtableTableId
- }),
- });
- // Handle the response
- } catch (error) {
- // Handle the error
- }
- };
-
- const fields = [{
- name: AIRTABLE_TOKEN_STR,
- label: AIRTABLE_TOKEN_STR,
+ const AIRTABLE_TOKEN_STR = 'Airtable Token';
+ const AIRTABLE_BASE_ID_STR = 'Airtable Base ID';
+ const AIRTABLE_TABLE_ID_STR = 'Airtable Table ID';
+
+ const onSubmit = async (event: React.FormEvent) => {
+ event.preventDefault();
+ // TODO: handle form submission
+ // const formData = new FormData(event.currentTarget);
+ // const airtableToken = formData.get(AIRTABLE_TOKEN_STR);
+ // const airtableBaseId = formData.get(AIRTABLE_BASE_ID_STR);
+ // const airtableTableId = formData.get(AIRTABLE_TABLE_ID_STR);
+
+ try {
+ // const response = await fetch('../api/crmconfig/airtable', {
+ // method: 'POST',
+ // headers: {
+ // 'Content-Type': 'application/json',
+ // },
+ // body: JSON.stringify({
+ // token: airtableToken,
+ // baseId: airtableBaseId,
+ // tableId: airtableTableId,
+ // }),
+ // });
+ // Handle the response
+ } catch (error) {
+ // Handle the error
+ }
+ };
+
+ const fields = [
+ {
+ name: AIRTABLE_TOKEN_STR,
+ label: AIRTABLE_TOKEN_STR,
},
{
- name: AIRTABLE_BASE_ID_STR,
- label: AIRTABLE_BASE_ID_STR,
+ name: AIRTABLE_BASE_ID_STR,
+ label: AIRTABLE_BASE_ID_STR,
},
{
- name: AIRTABLE_TABLE_ID_STR,
- label: AIRTABLE_TABLE_ID_STR,
- }];
-
-
- return (
-
-
-
- );
+ name: AIRTABLE_TABLE_ID_STR,
+ label: AIRTABLE_TABLE_ID_STR,
+ },
+ ];
+
+ return (
+
+
+
+ );
};
export default Page;
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index afde5b8..04fca07 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,30 +1,29 @@
-import type { Metadata } from "next";
-import { Inter } from "next/font/google";
-import { cn } from "@/lib/utils";
-import "../styles/globals.css";
+import type { Metadata } from 'next';
+import { Roboto } from 'next/font/google';
+import { PropsWithChildren } from 'react';
-import { NextAuthProvider } from "../components/nextAuthProvider"
+import { NextAuthProvider } from '@/components/nextAuthProvider';
+import { cn } from '@/lib/utils';
-const inter = Inter({ subsets: ["latin"] });
+import '../styles/globals.css';
+
+const sansFont = Roboto({
+ subsets: ['latin'],
+ weight: ['500', '700'],
+});
export const metadata: Metadata = {
- title: "Connie",
- description: "Generated by create next app",
+ title: 'Connie',
+ description:
+ 'A realtime communication center for community-based organizations',
};
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
-
+export default function RootLayout({ children }: PropsWithChildren) {
return (
-
-
- {children}
-
-
+
+ {children}
+
);
}
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
index 9f41b1a..27b6723 100644
--- a/src/app/login/page.tsx
+++ b/src/app/login/page.tsx
@@ -1,17 +1,16 @@
-"use client";
+'use client';
+
import { signIn, useSession } from 'next-auth/react';
+import { redirect } from 'next/navigation';
import React from 'react';
-import { redirect } from 'next/navigation'
-
-
export default function Login() {
- const {data: session, status} = useSession();
+ const { data: session, status } = useSession();
if (status === 'loading') {
return Loading...;
}
if (session) {
- redirect('/dashboard');
+ redirect('/dashboard');
}
return (
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 1a07f78..ff726b6 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,16 +1,70 @@
-"use client";
+'use client';
-import { useSession } from 'next-auth/react';
-import React from 'react';
-import { redirect } from 'next/navigation'
+import { signIn, useSession } from 'next-auth/react';
+import Image from 'next/image';
+import Link from 'next/link';
+import { redirect } from 'next/navigation';
+import asaLogo from '@/assets/asa-logo.png';
+import connieLogo from '@/assets/connie-logo.png';
+import { Button } from '@/components/ui/button';
+import { Card } from '@/components/ui/card';
+
+/**
+ * The landing page for Connie.
+ *
+ * Route: /
+ */
export default function Home() {
const { data: session, status } = useSession();
- if (status === 'loading') {
- return Loading...;
- } else if (session) {
+ if (session) {
redirect('/dashboard');
} else {
- redirect('/login');
+ return (
+
+
+
+
+
+
+
+ {status === 'loading' ? (
+
Loading...
+ ) : (
+
+ )}
+
+
+
+ Don't have an account?
+
+ Contact your organization admin.
+
+
+
+
+
+
+
+
+
+ );
}
-}
\ No newline at end of file
+}
diff --git a/src/assets/asa-logo.png b/src/assets/asa-logo.png
new file mode 100644
index 0000000..0ed24a0
Binary files /dev/null and b/src/assets/asa-logo.png differ
diff --git a/src/assets/connie-logo.png b/src/assets/connie-logo.png
new file mode 100644
index 0000000..3fe9022
Binary files /dev/null and b/src/assets/connie-logo.png differ
diff --git a/src/components/appbar/Dialpad.tsx b/src/components/appbar/Dialpad.tsx
index df556e0..fbdfd3f 100644
--- a/src/components/appbar/Dialpad.tsx
+++ b/src/components/appbar/Dialpad.tsx
@@ -1,19 +1,20 @@
-"use client";
-import { formatPhoneNumber, formatTime } from "@/lib/utils";
-import { useEffect, useState } from "react";
+'use client';
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
import {
- Phone,
Delete,
+ Hash,
+ MessageSquare,
MicOff,
- Video,
PauseCircle,
- MessageSquare,
- Hash,
+ Phone,
StickyNote,
-} from "lucide-react";
+ Video,
+} from 'lucide-react';
+import { useEffect, useState } from 'react';
+
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { formatPhoneNumber, formatTime } from '@/lib/utils';
export default function DialPad({
number,
diff --git a/src/components/appbar/Logo.tsx b/src/components/appbar/Logo.tsx
index 583029e..c5e9fbe 100644
--- a/src/components/appbar/Logo.tsx
+++ b/src/components/appbar/Logo.tsx
@@ -4,7 +4,11 @@ import Image from 'next/image';
export default function Logo() {
return (
-
+
@@ -101,7 +99,7 @@ export default function Appbar({
diff --git a/src/components/audittable/AuditTable.tsx b/src/components/audittable/AuditTable.tsx
index 3c07556..45ed0e6 100644
--- a/src/components/audittable/AuditTable.tsx
+++ b/src/components/audittable/AuditTable.tsx
@@ -1,4 +1,4 @@
-"use client";
+'use client';
import React, {
useEffect,
@@ -8,12 +8,12 @@ import React, {
import axios from "axios";
import * as XLSX from "xlsx";
-import { FetchedCalls, columns } from "./columns";
-import { DataTable } from "./DataTable";
-import { Button } from "../ui/button";
-import { Switch } from "../ui/switch";
-import { Label } from "../ui/label";
-import { Input } from "../ui/input";
+import { Button } from '../ui/button';
+import { Input } from '../ui/input';
+import { Label } from '../ui/label';
+import { Switch } from '../ui/switch';
+import { DataTable } from './DataTable';
+import { columns } from './columns';
interface SelectionChange {
selectionState: Record;
@@ -32,13 +32,13 @@ const AuditTable: React.FC = () => {
setLoading(true);
try {
const endpoint = showCalls
- ? "/api/audit-log/calls"
- : "/api/audit-log/message";
+ ? '/api/audit-log/calls'
+ : '/api/audit-log/message';
const response = await axios.get(endpoint);
setData(response.data);
setLoading(false);
} catch (error) {
- console.log("error fetching calls", error);
+ console.log('error fetching calls', error);
setLoading(false);
}
};
@@ -77,13 +77,13 @@ const AuditTable: React.FC = () => {
diff --git a/src/components/audittable/DataTable.tsx b/src/components/audittable/DataTable.tsx
index 20950e0..994c272 100644
--- a/src/components/audittable/DataTable.tsx
+++ b/src/components/audittable/DataTable.tsx
@@ -1,4 +1,4 @@
-"use client";
+'use client';
import {
ColumnDef,
@@ -98,7 +98,7 @@ export function DataTable({
table.getRowModel().rows.map((row) => (
{row.getVisibleCells().map((cell) => (
diff --git a/src/components/audittable/columns.tsx b/src/components/audittable/columns.tsx
index 3286697..00cc549 100644
--- a/src/components/audittable/columns.tsx
+++ b/src/components/audittable/columns.tsx
@@ -1,10 +1,10 @@
-"use client"
+'use client';
-import { ColumnDef } from "@tanstack/react-table"
-import { MoreHorizontal } from "lucide-react"
-import { Checkbox } from "@/components/ui/checkbox"
+import { ColumnDef } from '@tanstack/react-table';
+import { MoreHorizontal } from 'lucide-react';
-import { Button } from "@/components/ui/button"
+import { Button } from '@/components/ui/button';
+import { Checkbox } from '@/components/ui/checkbox';
import {
DropdownMenu,
DropdownMenuContent,
@@ -12,8 +12,7 @@ import {
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-
+} from '@/components/ui/dropdown-menu';
// This type is used to define the shape of our data.
export type FetchedCalls = {
@@ -22,25 +21,25 @@ export type FetchedCalls = {
from: string | null;
to: string | null;
timestamp: string;
-}
+};
export const columns: ColumnDef[] = [
{
- id: "select",
+ id: 'select',
header: ({ table }) => (
table.toggleAllPageRowsSelected(!!value)}
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
row.toggleSelected(!!value)}
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
@@ -49,27 +48,27 @@ export const columns: ColumnDef[] = [
},
{
- accessorKey: "direction",
- header: "Direction",
+ accessorKey: 'direction',
+ header: 'Direction',
},
{
- accessorKey: "from",
- header: "From",
+ accessorKey: 'from',
+ header: 'From',
},
{
- accessorKey: "to",
- header: "To",
+ accessorKey: 'to',
+ header: 'To',
},
{
- accessorKey: "timestamp",
- header: "Timestamp",
+ accessorKey: 'timestamp',
+ header: 'Timestamp',
},
{
- id: "actions",
+ id: 'actions',
cell: ({ row }) => {
- const call = row.original
+ const call = row.original;
return (
@@ -90,8 +89,7 @@ export const columns: ColumnDef[] = [
View details
- )
+ );
},
},
-
-]
+];
diff --git a/src/components/clientTable/ClientTable.tsx b/src/components/clientTable/ClientTable.tsx
index 56e4b5d..29fa78b 100644
--- a/src/components/clientTable/ClientTable.tsx
+++ b/src/components/clientTable/ClientTable.tsx
@@ -1,15 +1,21 @@
-"use client";
-import { useState } from "react";
-import { useSession } from "next-auth/react";
+'use client';
import {
ColumnDef,
+ RowData,
flexRender,
getCoreRowModel,
getFilteredRowModel,
- useReactTable,
getPaginationRowModel,
-} from "@tanstack/react-table";
+ useReactTable,
+} from '@tanstack/react-table';
+import { useSession } from 'next-auth/react';
+import { useState } from 'react';
+
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Separator } from '@/components/ui/separator';
import {
Table,
TableBody,
@@ -17,29 +23,32 @@ import {
TableHead,
TableHeader,
TableRow,
-} from "@/components/ui/table";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
-import { Separator } from "@/components/ui/separator";
-
-import { copyText } from "@/lib/utils";
-import useCalls from "@/lib/hooks/useCalls";
-import { CRMEntry } from "@/lib/crm/types";
+} from '@/components/ui/table';
+import { CRMEntry } from '@/lib/crm/types';
+import useCalls from '@/lib/hooks/useCalls';
+import { copyText } from '@/lib/utils';
interface DataTableProps {
columns: ColumnDef[];
data: CRMEntry[];
}
+declare module '@tanstack/table-core' {
+ // eslint-disable-next-line
+ interface TableMeta {
+ onPhoneNumberCopy: (number: string) => void;
+ onMakeCall: (number: string) => void;
+ }
+}
+
export function ClientTable({ columns, data }: DataTableProps) {
- const [globalFilter, setGlobalFilter] = useState("");
+ const [globalFilter, setGlobalFilter] = useState('');
const { data: session } = useSession();
const { makeCall } = useCalls({
- email: session?.user?.email ?? "",
- workerSid: "WK3b277b4e6a1d67f2240477fa33f75ea4", // TODO link twilio worker id with okta
- friendlyName: session?.user?.name ?? "",
+ email: session?.user?.email ?? '',
+ workerSid: 'WK3b277b4e6a1d67f2240477fa33f75ea4', // TODO link twilio worker id with okta
+ friendlyName: session?.user?.name ?? '',
});
const table = useReactTable({
@@ -73,7 +82,7 @@ export function ClientTable({ columns, data }: DataTableProps) {
setGlobalFilter(String(e.target.value))}
/>
@@ -93,9 +102,9 @@ export function ClientTable({ columns, data }: DataTableProps) {
{header.isPlaceholder
? null
: flexRender(
- header.column.columnDef.header,
- header.getContext()
- )}
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
);
})}
@@ -107,13 +116,13 @@ export function ClientTable({ columns, data }: DataTableProps) {
rows.map((row) => (
{row.getVisibleCells().map((cell) => (
{flexRender(
cell.column.columnDef.cell,
- cell.getContext()
+ cell.getContext(),
)}
))}
diff --git a/src/components/clientTable/columns.tsx b/src/components/clientTable/columns.tsx
index ae1ca6d..b6fc92c 100644
--- a/src/components/clientTable/columns.tsx
+++ b/src/components/clientTable/columns.tsx
@@ -1,27 +1,28 @@
-"use client";
-import Link from "next/link";
+'use client';
-import { ColumnDef } from "@tanstack/react-table";
-import { Checkbox } from "@/components/ui/checkbox";
+import { ColumnDef } from '@tanstack/react-table';
+import { MoreVertical } from 'lucide-react';
+import Link from 'next/link';
+
+import { Button } from '@/components/ui/button';
+import { Checkbox } from '@/components/ui/checkbox';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
-import { Button } from "@/components/ui/button";
-import { MoreVertical } from "lucide-react";
-import { CRMEntry } from "@/lib/crm/types";
+} from '@/components/ui/dropdown-menu';
+import { CRMEntry } from '@/lib/crm/types';
export const columns: ColumnDef[] = [
{
- id: "select",
+ id: 'select',
header: ({ table }) => (
table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
@@ -39,26 +40,26 @@ export const columns: ColumnDef[] = [
},
{
header: () => Id
,
- accessorKey: "id",
+ accessorKey: 'id',
},
{
- accessorKey: "full_name",
+ accessorKey: 'full_name',
header: () => Client
,
},
{
- accessorKey: "Phone",
+ accessorKey: 'Phone',
header: () => Phone Number
,
},
{
- accessorKey: "email",
+ accessorKey: 'email',
header: () => Email
,
},
{
- accessorKey: "lastContacted",
+ accessorKey: 'lastContacted',
header: () => Last contacted
,
},
{
- id: "actions",
+ id: 'actions',
enableHiding: false,
cell: ({ row, table }) => {
const client = row.original;
@@ -82,7 +83,8 @@ export const columns: ColumnDef[] = [
*/}
- (table.options.meta as any)?.onPhoneNumberCopy(client.Phone)
+ // TODO: Handle type safety
+ table.options.meta?.onPhoneNumberCopy(client.Phone)
}
>
Copy phone number
diff --git a/src/components/nextAuthProvider.tsx b/src/components/nextAuthProvider.tsx
index dc54b04..a8a93c5 100644
--- a/src/components/nextAuthProvider.tsx
+++ b/src/components/nextAuthProvider.tsx
@@ -1,12 +1,11 @@
-"use client";
+'use client';
-import { SessionProvider } from "next-auth/react";
+import { SessionProvider } from 'next-auth/react';
type Props = {
children?: React.ReactNode;
};
export const NextAuthProvider = ({ children }: Props) => {
-
return {children};
-};
\ No newline at end of file
+};
diff --git a/src/components/notes/Note.tsx b/src/components/notes/Note.tsx
index 2804eed..3553ae5 100644
--- a/src/components/notes/Note.tsx
+++ b/src/components/notes/Note.tsx
@@ -1,28 +1,29 @@
-import dayjs from "dayjs";
-import localizedFormat from "dayjs/plugin/localizedFormat";
-import { NoteData } from "@/types/noteInterface";
+import dayjs from 'dayjs';
+import localizedFormat from 'dayjs/plugin/localizedFormat';
+
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
-} from "@/components/ui/card";
+} from '@/components/ui/card';
+import { InteractionData } from '@/types/notes';
-export default function Note({ note }: { note: NoteData }) {
+/**
+ * A component that displays a case note for a client made by an agent.
+ */
+export default function Note({ note }: { note: InteractionData }) {
dayjs.extend(localizedFormat);
return (
-
+
{note.author}
- {dayjs(note.dateCreated).format("LLL")}
+ {dayjs(note.dateCreated).format('LLL')}
diff --git a/src/components/notes/index.tsx b/src/components/notes/index.tsx
index 153c502..4ebdb50 100644
--- a/src/components/notes/index.tsx
+++ b/src/components/notes/index.tsx
@@ -1,26 +1,27 @@
-"use client";
-import { useState, useEffect, ChangeEvent } from "react";
-import { useSession } from "next-auth/react";
-import dayjs from "dayjs";
+'use client';
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { ScrollArea } from "@/components/ui/scroll-area";
-import { Skeleton } from "@/components/ui/skeleton";
-import { Textarea } from "@/components/ui/textarea";
-import { Search } from "lucide-react";
+import dayjs from 'dayjs';
+import { Search } from 'lucide-react';
+import { useSession } from 'next-auth/react';
+import { ChangeEvent, useEffect, useState } from 'react';
-import Note from "./Note";
-import { NoteData } from "@/types/noteInterface";
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { ScrollArea } from '@/components/ui/scroll-area';
+import { Skeleton } from '@/components/ui/skeleton';
+import { Textarea } from '@/components/ui/textarea';
+import { InteractionData } from '@/types/notes';
+
+import Note from './Note';
export default function Notes({ clientId }: { clientId: string }) {
- const [notes, setNotes] = useState([]);
+ const [notes, setNotes] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
- const [searchItem, setSearchItem] = useState("");
- const [filteredNotes, setFilteredNotes] = useState([]);
+ const [searchItem, setSearchItem] = useState('');
+ const [filteredNotes, setFilteredNotes] = useState([]);
- const [newNote, setNewNote] = useState("");
+ const [newNote, setNewNote] = useState('');
const { data: session } = useSession();
@@ -29,11 +30,11 @@ export default function Notes({ clientId }: { clientId: string }) {
setLoading(true);
try {
const response = await fetch(
- `${process.env.NEXT_PUBLIC_URL}/api/notes?clientId=${clientId}`
+ `${process.env.NEXT_PUBLIC_URL}/api/notes?clientId=${clientId}`,
);
const data = await response.json();
- data.sort((a: NoteData, b: NoteData) =>
- dayjs(b.dateCreated).diff(dayjs(a.dateCreated))
+ data.sort((a: InteractionData, b: InteractionData) =>
+ dayjs(b.dateCreated).diff(dayjs(a.dateCreated)),
);
setNotes(data);
setFilteredNotes(data);
@@ -42,7 +43,7 @@ export default function Notes({ clientId }: { clientId: string }) {
} catch (e) {
console.error(e);
setLoading(false);
- setError("There was an error loading the notes");
+ setError('There was an error loading the notes');
}
};
@@ -51,7 +52,7 @@ export default function Notes({ clientId }: { clientId: string }) {
async function handleAddNote(note: string) {
const newNote = {
- author: session?.user?.name ?? "Agent",
+ author: session?.user?.name ?? 'Agent',
callDate: new Date().toISOString(),
dateCreated: new Date().toISOString(),
dateUpdated: new Date().toISOString(),
@@ -63,10 +64,10 @@ export default function Notes({ clientId }: { clientId: string }) {
const response = await fetch(
`${process.env.NEXT_PUBLIC_URL}/api/notes?clientId=${clientId}`,
{
- method: "POST",
- headers: { "Content-Type": "application/json" },
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ note: newNote }),
- }
+ },
);
const data = await response.json();
@@ -86,11 +87,11 @@ export default function Notes({ clientId }: { clientId: string }) {
...filteredNotes,
]);
}
- setNewNote("");
+ setNewNote('');
setError(null);
} catch (e) {
console.error(e);
- setError("There was an error saving the note.");
+ setError('There was an error saving the note.');
}
}
@@ -99,7 +100,7 @@ export default function Notes({ clientId }: { clientId: string }) {
setSearchItem(searchTerm);
const filteredNotes = notes.filter((note) =>
- note.content.toLowerCase().includes(searchTerm)
+ note.content.toLowerCase().includes(searchTerm),
);
setFilteredNotes(filteredNotes);
@@ -143,7 +144,7 @@ export default function Notes({ clientId }: { clientId: string }) {
/>