├── .gitignore ├── LICENSE ├── README.md ├── bill-bot-baggins ├── .eslintrc.json ├── .gitignore ├── .prettierrc.json ├── app │ ├── (admin) │ │ ├── (routes) │ │ │ └── admin │ │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── (auth) │ │ └── (routes) │ │ │ └── sign-in │ │ │ └── [[...sign-in]] │ │ │ └── page.tsx │ ├── (client) │ │ ├── (routes) │ │ │ └── invoice │ │ │ │ └── [invoiceId] │ │ │ │ └── page.tsx │ │ └── layout.tsx │ ├── (main) │ │ └── (routes) │ │ │ └── page.tsx │ ├── favicon.ico │ ├── globals.css │ └── layout.tsx ├── components.json ├── components │ ├── AdminAuth.tsx │ ├── Columns.tsx │ ├── DataTable.tsx │ ├── Footer.tsx │ ├── InvoiceDisplay.tsx │ ├── InvoiceSection.tsx │ ├── Navbar.tsx │ ├── ParticleEffect.tsx │ ├── Profile.tsx │ ├── RecentSales.tsx │ ├── TeamMember.tsx │ ├── index.ts │ ├── overview.tsx │ └── ui │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── table.tsx │ │ └── tabs.tsx ├── environment.d.ts ├── lib │ ├── api.ts │ ├── types.ts │ ├── utils.ts │ └── validations │ │ └── form.ts ├── middleware.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public │ ├── ales-nesetril-background.jpg │ ├── archive │ │ ├── big-logo.png │ │ └── logo.png │ ├── bg-pattern.jpeg │ ├── big-logo.png │ ├── dashboard-2.png │ ├── github-mark-white.png │ ├── invoice.png │ ├── logo.png │ ├── splashpage.png │ └── up-down.svg ├── tailwind.config.ts └── tsconfig.json ├── pull_request_template.md ├── salesforce-webhook ├── Dockerfile ├── eventHandler.js ├── package.json ├── routers │ ├── authRouter.js │ ├── salesforceRouter.js │ └── stripeRouter.js ├── salesforce-pub-sub-api.mjs └── server.mjs └── stripe-webhook ├── Dockerfile ├── package-lock.json ├── package.json ├── route.js └── stripe_salesforce_fetch.js /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | /salesforce-webhook/.env 3 | /.env 4 | /stripe-webhook/.env 5 | # dependencies 6 | */node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | dump.rdb 24 | /.vscode 25 | 26 | # debug 27 | npm-debug.log* 28 | yarn-debug.log* 29 | yarn-error.log* 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | next-env.d.ts 40 | 41 | .env 42 | node_modules 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | 21 | [![Contributors][contributors-shield]][contributors-url] 22 | [![Forks][forks-shield]][forks-url] 23 | [![Stargazers][stars-shield]][stars-url] 24 | [![Issues][issues-shield]][issues-url] 25 | [![MIT License][license-shield]][license-url] 26 | [![LinkedIn][linkedin-shield]][linkedin-url] 27 | 28 | 29 | 30 |
31 |
32 | 33 | Logo 34 | 35 | 36 |

PayStream

37 | 38 |

39 | PayStream is a Next.js application designed to streamline financial management for a non-profit client. 40 |
41 | Explore the docs » 42 |
43 |
44 | Report Bug 45 | · 46 | Request Feature 47 |

48 |
49 | 50 | 51 | 52 | 53 |
54 | Table of Contents 55 |
    56 |
  1. 57 | About The Project 58 | 61 |
  2. 62 |
  3. 63 | Getting Started 64 | 68 |
  4. 69 |
  5. Usage
  6. 70 |
  7. Roadmap
  8. 71 |
  9. Contributing
  10. 72 |
  11. License
  12. 73 |
  13. Contact
  14. 74 |
  15. Acknowledgments
  16. 75 |
76 |
77 | 78 | 79 | 80 | 81 | ## About The Project 82 | 83 |

(back to top)

84 | 85 | ### Built With 86 | 87 | * [![NextJS][NextJs]][NextJS-url] 88 | * [![React][React.js]][React-url] 89 | * [![JavaScript][JavaScript]][JavaScript-url] 90 | * [![Typescript][TS.js]][TS-url] 91 | * [![Tailwind][Tailwind]][Tailwind-url] 92 | * [![][Git]][Git-url] 93 | * [![Jest][Jest]][Jest-url] 94 | * [![AWS][AWS]][AWS-url] 95 | * [![Docker][Docker]][Docker-url] 96 | 97 | 98 |

(back to top)

99 | 100 | 101 | 102 | 103 | ## Getting Started 104 | 105 | ### Prerequisites 106 | 107 | Before using this application, make sure you have the following in place: 108 | 109 | 1. **Stripe Account:** You’ll need a Stripe account to connect to the payment gateway and manage donations. 110 | 2. **Salesforce Account:** You must have a Salesforce account with the necessary permissions to set up the integration. 111 | 3. **Web Server:** This application requires a web server to host the integration. You can use a cloud service like AWS, Google Cloud, or Heroku. 112 | 4. **API Keys:** Obtain API keys from both Stripe and Salesforce for secure communication between the systems. 113 | 114 | ### Installation 115 | 116 | 1. Get your publishable and secret Stripe API Keys at: [https://dashboard.stripe.com/test/apikeys](https://dashboard.stripe.com/test/apikeys) 117 | 2. Get your Salesforce access token by following the following steps: [https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_jwt_flow.htm&type=5](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_jwt_flow.htm&type=5) 118 | 3. Clone the repo 119 | ```sh 120 | git clone https://github.com/oslabs-beta/PayStream.git 121 | ``` 122 | 4. Install NPM packages 123 | ```sh 124 | npm install 125 | ``` 126 | 5. Enter your JWT Secret into the `.env` file: 127 | ```js 128 | JWT_SECRET = 'ENTER YOUR JWT SECRET'; 129 | ``` 130 | 6. Enter your Salesforce Authentication information into the `.env` file: 131 | ```js 132 | SALESFORCE_GRAPHQL_URI = '[YOUR SALESFORCE INSTANCE]/services/data/v58.0/graphql' 133 | SALESFORCE_COOKIE_AUTH = 'ENTER YOUR SALEFORCE COOKING AUTHORIZATION' 134 | SALESFORCE_LOGIN_URL = 'ENTER YOUR SALESFORCE LOGIN URL' 135 | SALESFORCE_AUTH_TYPE = oauth-client-credentials 136 | SALESFORCE_USERNAME = 'ENTER YOUR SALESFORCE USERNAME' 137 | SALESFORCE_PASSWORD = 'ENTER YOUR SALESFORCE PASSWORD' 138 | SALESFORCE_ORG_ID = 'ENTER YOUR SALESFORCE ORGANIZATION ID' 139 | PUB_SUB_ENDPOINT = api.pubsub.salesforce.com:7443 140 | SALESFORCE_CLIENT_ID = 'ENTER YOUR SALESFORCE CLIENT ID' 141 | SALESFORCE_CLIENT_SECRET = 'ENTER YOUR SALESFORCE CLIENT SECRET' 142 | SALESFORCE_URL = 'https://test.salesforce.com' 143 | BASE64_PRIVATE_KEY = 'ENTER YOUR BASE64 PRIVATE KEY' 144 | ``` 145 | 146 | 7. Enter your Stripe Authentication information into the `.env` file: 147 | ```js 148 | NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY = 'ENTER YOUR STRIPE PUBLIC KEY'; 149 | STRIPE_SECRET_KEY = 'ENTER YOUR STRIPE SECRET KEY'; 150 | STRIPE_ENDPOINT_SECRET = 'ENTER YOUR STRIPE ENDPOINT SECRET' 151 | ``` 152 | 153 | 8. Enter your Clerk Authentication information into the `.env` file: 154 | ```js 155 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = 'ENTER YOUR CLERK PUBLIC KEY'; 156 | CLERK_SECRET_KEY = 'ENTER YOUR CLERK SECRET KEY'; 157 | 158 | NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in 159 | NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/ 160 | ``` 161 | 162 |

(back to top)

163 | 164 | ## Configuration 165 | 1. **Stripe Integration:** 166 | - Go to your Stripe dashboard and obtain your API keys. 167 | - Update the `.env` file with your Stripe API keys. 168 | - Go to [https://dashboard.stripe.com/test/apikeys](https://dashboard.stripe.com/test/webhooks) and generate your Stripe endpoint for your application. 169 | 2. **Salesforce Integration:** 170 | - Obtain Salesforce API credentials (Consumer Key, Consumer Secret, Username, and Password). 171 | - Update the `.env` file with your Salesforce API credentials. 172 | - The queries are mapped to custom payment records for a specific client and will need to be refactored to your application specific needs. 173 | 3. **Webhook Setup:** 174 | - Configure webhooks in Stripe to send events to your application’s endpoint. 175 | - Use a service like ngrok to create a secure tunnel to your local server or set up SSL on your production server. 176 | 4. **Customization:** 177 | - Modify the application to match your nonprofit’s specific Salesforce objects, fields, and donation processing logic. 178 | 179 | 180 | ## Screenshots 181 | 182 |
183 | 184 | splash 185 | invoice 186 | dashboard 187 | 188 |
189 | 190 | 191 | 192 | ## Contributing 193 | 194 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 195 | 196 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". 197 | Don't forget to give the project a star! Thanks again! 198 | 199 | 1. Fork the Project 200 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 201 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 202 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 203 | 5. Open a Pull Request 204 | 205 |

(back to top)

206 | 207 | 208 | 209 | 210 | ## License 211 | 212 | Distributed under the MIT License. See `LICENSE.txt` for more information. 213 | 214 |

(back to top)

215 | 216 | 217 | 218 | 219 | ## Contact 220 | 221 | Email Us: paystreamdevops@gmail.com 222 | 223 | Project Link: [https://github.com/oslabs-beta/PayStream](https://github.com/oslabs-beta/PayStream) 224 | 225 |

(back to top)

226 | 227 | 228 | 229 | ## Authors 230 | | Developed By | Github | LinkedIn | 231 | | :----------: | :---------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------: | 232 | | Chandler Charity | [![Github](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/lcchrty) | [![LinkedIn](https://img.shields.io/badge/LinkedIn-%230077B5.svg?logo=linkedin&logoColor=white)](https://www.linkedin.com/in/chandlerchrty/) | 233 | | Julia Xin | [![Github](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/juliazlx) | 234 | | Liam Hodges | [![Github](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/lhodges3) | [![LinkedIn](https://img.shields.io/badge/LinkedIn-%230077B5.svg?logo=linkedin&logoColor=white)](https://www.linkedin.com/in/liam-p-hodges/) | 235 | | Robert Hoover | [![Github](https://img.shields.io/badge/github-%23121011.svg?style=for-the-badge&logo=github&logoColor=white)](https://github.com/Gambarou) | [![LinkedIn](https://img.shields.io/badge/LinkedIn-%230077B5.svg?logo=linkedin&logoColor=white)](https://www.linkedin.com/in/roberthoover00/) | 236 | 237 | 238 | 239 |

(back to top)

240 | 241 | 242 | 243 | 244 | 245 | [contributors-shield]: https://img.shields.io/github/contributors/oslabs-beta/PayStream.svg?style=for-the-badge 246 | [contributors-url]: https://github.com/oslabs-beta/PayStream/graphs/contributors 247 | [forks-shield]: https://img.shields.io/github/forks/oslabs-beta/PayStream.svg?style=for-the-badge 248 | [forks-url]: https://github.com/oslabs-beta/PayStream/network/members 249 | [stars-shield]: https://img.shields.io/github/stars/oslabs-beta/PayStream.svg?style=for-the-badge 250 | [stars-url]: https://github.com/oslabs-beta/PayStream/stargazers 251 | [issues-shield]: https://img.shields.io/github/issues/oslabs-beta/PayStream.svg?style=for-the-badge 252 | [issues-url]: https://github.com/oslabs-beta/PayStream/issues 253 | [license-shield]: https://img.shields.io/github/license/oslabs-beta/PayStream.svg?style=for-the-badge 254 | [license-url]: https://github.com/oslabs-beta/PayStream/blob/master/LICENSE.txt 255 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 256 | [linkedin-url]: https://www.linkedin.com/company/pay-stream-dev 257 | [product-screenshot]: images/screenshot.png 258 | [React.js]: https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB 259 | [React-url]: https://reactjs.org/ 260 | [TS.js]: https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white 261 | [TS-url]: https://www.typescriptlang.org/ 262 | [JavaScript]: https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E 263 | [JavaScript-url]: https://www.javascript.com/ 264 | [Jest]: https://img.shields.io/badge/-jest-%23C21325?style=for-the-badge&logo=jest&logoColor=white 265 | [Jest-url]: https://jestjs.io/ 266 | [Git]: https://img.shields.io/badge/git-%23F05033.svg?style=for-the-badge&logo=git&logoColor=white 267 | [Git-url]: https://git-scm.com/ 268 | [Tailwind]: https://img.shields.io/badge/Tailwind-%231DA1F2.svg?style=for-the-badge&logo=tailwind-css&logoColor=white 269 | [Tailwind-url]: https://tailwindcss.com/ 270 | [NextJS]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white 271 | [NextJS-url]: https://nextjs.org/ 272 | [AWS]: https://img.shields.io/badge/AWS-%231E73BE.svg?style=for-the-badge&logo=amazon-aws&logoColor=white: 273 | [AWS-url]: https://aws.amazon.com/ 274 | [Docker]: https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white 275 | [Docker-url]: https://www.docker.com/ 276 | -------------------------------------------------------------------------------- /bill-bot-baggins/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "prettier"] 3 | } 4 | -------------------------------------------------------------------------------- /bill-bot-baggins/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | dump.rdb 22 | /.vscode 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | 39 | .env 40 | -------------------------------------------------------------------------------- /bill-bot-baggins/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "semi": true, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "jsxSingleQuote": true, 7 | "plugins": ["prettier-plugin-tailwindcss"] 8 | 9 | } -------------------------------------------------------------------------------- /bill-bot-baggins/app/(admin)/(routes)/admin/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Card, 4 | CardContent, 5 | CardDescription, 6 | CardHeader, 7 | CardTitle, 8 | } from '@/components/ui/card'; 9 | import { Overview, RecentSales, columns, DataTable } from '@/components/index'; 10 | import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; 11 | import { getRevenueData } from '@/lib/utils'; 12 | import { getSalesForceAccessToken, getSalesForceInvoiceData } from '@/lib/api'; 13 | 14 | async function AdminDashboardPage() { 15 | const currentYear = new Date().getFullYear().toString(); 16 | // get SF accessToken 17 | const accessToken = await getSalesForceAccessToken(); 18 | // get SF invoice data using accessToken 19 | const data = await getSalesForceInvoiceData(accessToken); 20 | // get formatted revenue data 21 | const revenueData = getRevenueData(data); 22 | 23 | const { 24 | payments, 25 | monthRevenue, 26 | yearRevenue, 27 | outstandingInvoices, 28 | revenueDataByMonth, 29 | monthRevenueGrowth, 30 | yearRevenueGrowth, 31 | } = revenueData; 32 | 33 | return ( 34 |
35 |
36 |

Dashboard

37 |
38 |
39 | 40 | 41 | Overview 42 | Analytics 43 | 44 | 45 |
46 | 47 | 48 | 49 | Revenue YTD 50 | 51 | 61 | 62 | 63 | 64 | 65 |
{`${yearRevenue.toLocaleString( 66 | 'en-US', 67 | { 68 | style: 'currency', 69 | currency: 'USD', 70 | } 71 | )}`}
72 |

73 | {yearRevenueGrowth >= 0 && '+'} 74 | {`${yearRevenueGrowth}`}% from last year 75 |

76 |
77 |
78 | 79 | 80 | 81 | Revenue MTD 82 | 83 | 93 | 94 | 95 | 96 | 97 |
{`${monthRevenue.toLocaleString( 98 | 'en-US', 99 | { 100 | style: 'currency', 101 | currency: 'USD', 102 | } 103 | )}`}
104 |

105 | {monthRevenueGrowth >= 0 && '+'} 106 | {`${monthRevenueGrowth}% from last month`} 107 |

108 |
109 |
110 | 111 | 112 | 113 | # of Outstanding Invoices 114 | 115 | 125 | 126 | 127 | 128 | 129 | 130 |
131 | {`${outstandingInvoices}`} 132 |
133 |
134 |
135 |
136 |
137 | 138 | 139 | {`Overview of ${currentYear}`} 140 | 141 | 142 | {revenueDataByMonth ? ( 143 | 144 | ) : ( 145 |
146 | Error fetching data... 147 |
148 | )} 149 |
150 |
151 | 152 | 153 | Recent Payments 154 | 155 | There have been {payments.length} recent payments. 156 | 157 | 158 | 159 | 160 | 161 | 162 |
163 |
164 | 165 | {data ? ( 166 | 167 | ) : ( 168 |
169 | Error fetching data... 170 |
171 | )} 172 |
173 |
174 |
175 | ); 176 | } 177 | 178 | export default AdminDashboardPage; 179 | -------------------------------------------------------------------------------- /bill-bot-baggins/app/(admin)/layout.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from '@/components/Navbar'; 2 | 3 | export default function AdminLayout({ 4 | children, // will be a page or nested layout 5 | }: { 6 | children: React.ReactNode; 7 | }) { 8 | return ( 9 |
10 | 11 | {children} 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /bill-bot-baggins/app/(auth)/(routes)/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { AdminAuth } from '@/components/AdminAuth'; 2 | import ParticleEffect from '@/components/ParticleEffect'; 3 | import Image from 'next/image'; 4 | import Link from 'next/link'; 5 | import React from 'react'; 6 | 7 | function SignInPage() { 8 | return ( 9 |
10 |
11 |
12 | 13 |
14 | 15 | Billbot Baggins logo 23 | 24 |
25 |
26 |
27 |

28 | “PayStream: Where your payments journey begins, and 29 | financial adventures never feel like a quest.” 30 |

31 |
Bobby Tables
32 |
33 |
34 |
35 |
36 |
37 |
38 |

39 | Admin Login 40 |

41 |

42 | Enter your credentials below to sign in 43 |

44 |
45 | 46 |
47 |
48 |
49 | ); 50 | } 51 | 52 | export default SignInPage; 53 | -------------------------------------------------------------------------------- /bill-bot-baggins/app/(client)/(routes)/invoice/[invoiceId]/page.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Stripe from 'stripe'; 3 | 4 | import InvoiceDisplay from '@/components/InvoiceDisplay'; 5 | import { InvoiceId } from '@/lib/types'; 6 | import { getStripeInvoiceData } from '@/lib/api'; 7 | 8 | async function InvoicePage({ params }: { params: InvoiceId }) { 9 | const { invoiceId } = params; 10 | 11 | const data = await getStripeInvoiceData(invoiceId); 12 | 13 | return ( 14 |
15 | {data instanceof Stripe.errors.StripeError ? ( 16 | 17 | ) : ( 18 | 19 | )} 20 |
21 | ); 22 | } 23 | 24 | export default InvoicePage; 25 | -------------------------------------------------------------------------------- /bill-bot-baggins/app/(client)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Footer, Navbar } from '@/components/index'; 2 | 3 | export default function ClientLayout({ 4 | children, // will be a page or nested layout 5 | }: { 6 | children: React.ReactNode; 7 | }) { 8 | return ( 9 |
10 | 11 | {children} 12 |
13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /bill-bot-baggins/app/(main)/(routes)/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import Link from 'next/link'; 3 | import React from 'react'; 4 | import { Button } from '@/components/ui/button'; 5 | import TeamMember from '@/components/TeamMember'; 6 | 7 | async function Home() { 8 | return ( 9 |
10 |
11 |
12 |
13 | 14 | PayStream logo 22 | 23 |
24 |
25 |

Real-time updates,

26 |

Well-informed decisions

27 |
28 |
29 |

30 | PayStream provides real-time payment information so you can make 31 | well-informed business decisions based on your cash-flow metrics. 32 |

33 |
34 |
35 |
36 |

37 | PayStream is an Open Source product developed in collaboration 38 | with OS Labs. 39 |

40 |
41 | 44 | 49 |
50 |
51 |
52 |
53 |
54 |

55 | Meet the T e a m 56 |

57 |
58 | 64 | 70 | 76 | 82 |
83 |
84 |
85 |
86 |
87 | ); 88 | } 89 | export default Home; 90 | -------------------------------------------------------------------------------- /bill-bot-baggins/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/PayStream/873cc8533db29ff56061c8100f01476bf29394b4/bill-bot-baggins/app/favicon.ico -------------------------------------------------------------------------------- /bill-bot-baggins/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 20 14.3% 4.1%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 20 14.3% 4.1%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 20 14.3% 4.1%; 13 | --primary: 24.6 95% 53.1%; 14 | --primary-foreground: 60 9.1% 97.8%; 15 | --secondary: 60 4.8% 95.9%; 16 | --secondary-foreground: 24 9.8% 10%; 17 | --muted: 60 4.8% 95.9%; 18 | --muted-foreground: 25 5.3% 44.7%; 19 | --accent: 60 4.8% 95.9%; 20 | --accent-foreground: 24 9.8% 10%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 60 9.1% 97.8%; 23 | --border: 20 5.9% 90%; 24 | --input: 20 5.9% 90%; 25 | --ring: 24.6 95% 53.1%; 26 | --radius: 0.3rem; 27 | } 28 | 29 | .dark { 30 | --background: 20 14.3% 4.1%; 31 | --foreground: 60 9.1% 97.8%; 32 | --card: 20 14.3% 4.1%; 33 | --card-foreground: 60 9.1% 97.8%; 34 | --popover: 20 14.3% 4.1%; 35 | --popover-foreground: 60 9.1% 97.8%; 36 | --primary: 20.5 90.2% 48.2%; 37 | --primary-foreground: 60 9.1% 97.8%; 38 | --secondary: 12 6.5% 15.1%; 39 | --secondary-foreground: 60 9.1% 97.8%; 40 | --muted: 12 6.5% 15.1%; 41 | --muted-foreground: 24 5.4% 63.9%; 42 | --accent: 12 6.5% 15.1%; 43 | --accent-foreground: 60 9.1% 97.8%; 44 | --destructive: 0 72.2% 50.6%; 45 | --destructive-foreground: 60 9.1% 97.8%; 46 | --border: 12 6.5% 15.1%; 47 | --input: 12 6.5% 15.1%; 48 | --ring: 20.5 90.2% 48.2%; 49 | } 50 | } 51 | 52 | @layer base { 53 | * { 54 | @apply border-border; 55 | } 56 | body { 57 | @apply bg-background text-foreground; 58 | 59 | 60 | } 61 | } 62 | 63 | body::-webkit-scrollbar { 64 | display: none; /* for Chrome, Safari, and Opera */ 65 | } 66 | 67 | 68 | /* This will get rid of the default white background when you select an autofill for an input*/ 69 | input:-webkit-autofill, 70 | input:-webkit-autofill:hover, 71 | input:-webkit-autofill:focus, 72 | input:-webkit-autofill:active{ 73 | -webkit-text-fill-color: #ffffff; 74 | transition: background-color 5000s ease-in-out 0s; 75 | } -------------------------------------------------------------------------------- /bill-bot-baggins/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import '@/app/globals.css'; 2 | 3 | import type { Metadata } from 'next'; 4 | import { Open_Sans } from 'next/font/google'; 5 | import { ClerkProvider } from '@clerk/nextjs'; 6 | import { dark } from '@clerk/themes'; 7 | const font = Open_Sans({ subsets: ['latin'] }); 8 | 9 | export const metadata: Metadata = { 10 | title: 'PayStream', 11 | description: 'Generated by PLTG', 12 | }; 13 | 14 | export default async function RootLayout({ 15 | children, 16 | }: { 17 | children: React.ReactNode; 18 | }) { 19 | return ( 20 | 25 | 26 | 27 | 28 | 29 | {children} 30 | 31 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /bill-bot-baggins/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /bill-bot-baggins/components/AdminAuth.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { useState } from 'react'; 4 | 5 | import * as z from 'zod'; 6 | import { zodResolver } from '@hookform/resolvers/zod'; 7 | import { useSignIn } from '@clerk/nextjs'; 8 | import { useRouter } from 'next/navigation'; 9 | import { useForm } from 'react-hook-form'; 10 | import { AlertTriangle } from 'lucide-react'; 11 | import { 12 | Form, 13 | FormControl, 14 | FormField, 15 | FormItem, 16 | FormLabel, 17 | FormMessage, 18 | } from '@/components/ui/form'; 19 | import { Input } from '@/components/ui/input'; 20 | import { Button } from '@/components/ui/button'; 21 | import { loginFormSchema } from '@/lib/validations/form'; 22 | import { cn } from '@/lib/utils'; 23 | 24 | interface UserAuthFormProps extends React.HTMLAttributes {} 25 | 26 | export function AdminAuth({ className, ...props }: UserAuthFormProps) { 27 | const { isLoaded, signIn, setActive } = useSignIn(); 28 | const [isSubmitting, setIsSubmitting] = useState(false); 29 | const [isError, setIsError] = useState(false); 30 | 31 | const router = useRouter(); 32 | 33 | async function onSubmit(values: z.infer) { 34 | // clerk provided function 35 | if (!isLoaded) { 36 | return null; 37 | } 38 | // clears error warning when a user re-submits a different username or password 39 | setIsError(false); 40 | 41 | setIsSubmitting(true); 42 | 43 | // attempts to login a user in Clerk with provided credentials 44 | try { 45 | const result = await signIn.create({ 46 | identifier: values.email, 47 | password: values.password, 48 | }); 49 | 50 | // If successful sets an active session and redirects to /admin 51 | if (result.status === 'complete') { 52 | await setActive({ session: result.createdSessionId }); 53 | router.push('/admin'); 54 | // not sure about the else here, will revisit 55 | } else { 56 | console.log(result); 57 | } 58 | // if there was an error logging in (wrong password/email) 59 | // sets an error so that it will be displayed to the user 60 | } catch (err: any) { 61 | console.log(err); 62 | setIsSubmitting(false); 63 | setIsError(true); 64 | } 65 | } 66 | 67 | // zod form validation, makes sure the user has inputted a proper email 68 | // and that the password is at least 8 chars long 69 | const form = useForm>({ 70 | resolver: zodResolver(loginFormSchema), 71 | defaultValues: { 72 | email: '', 73 | password: '', 74 | }, 75 | }); 76 | 77 | return ( 78 |
79 | {isError && ( 80 |
81 | 82 |

Invalid username or password. Please try again.

83 |
84 | )} 85 |
86 | 87 | ( 91 | 92 | Email 93 | 94 | 99 | 100 | 101 | 102 | )} 103 | /> 104 | ( 108 | 109 | Password 110 | 111 | 117 | 118 | 119 | 120 | )} 121 | /> 122 | 131 | 132 | 133 |
134 | ); 135 | } 136 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/Columns.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ColumnDef } from '@tanstack/react-table'; 4 | import { PaymentProps } from '@/lib/types'; 5 | import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react'; 6 | 7 | export const columns: ColumnDef[] = [ 8 | { 9 | accessorKey: 'invoice_id', 10 | header: ({ column }) => { 11 | return ( 12 |
column.toggleSorting()} 15 | > 16 | Invoice ID 17 | {column.getIsSorted() === 'asc' ? ( 18 | 19 | ) : column.getIsSorted() === 'desc' ? ( 20 | 21 | ) : ( 22 | 23 | )} 24 |
25 | ); 26 | }, 27 | }, 28 | { 29 | accessorKey: 'account_name', 30 | header: ({ column }) => { 31 | return ( 32 |
column.toggleSorting()} 35 | > 36 | Client 37 | {column.getIsSorted() === 'asc' ? ( 38 | 39 | ) : column.getIsSorted() === 'desc' ? ( 40 | 41 | ) : ( 42 | 43 | )} 44 |
45 | ); 46 | }, 47 | }, 48 | { 49 | accessorKey: 'project_name', 50 | header: ({ column }) => { 51 | return ( 52 |
column.toggleSorting()} 55 | > 56 | Project 57 | {column.getIsSorted() === 'asc' ? ( 58 | 59 | ) : column.getIsSorted() === 'desc' ? ( 60 | 61 | ) : ( 62 | 63 | )} 64 |
65 | ); 66 | }, 67 | }, 68 | { 69 | accessorKey: 'amount', 70 | header: ({ column }) => { 71 | return ( 72 |
column.toggleSorting()} 75 | > 76 | Amount 77 | {column.getIsSorted() === 'asc' ? ( 78 | 79 | ) : column.getIsSorted() === 'desc' ? ( 80 | 81 | ) : ( 82 | 83 | )} 84 |
85 | ); 86 | }, 87 | cell: ({ row }) => { 88 | const amount = parseFloat(row.getValue('amount')); 89 | const formatted = new Intl.NumberFormat('en-US', { 90 | style: 'currency', 91 | currency: 'USD', 92 | }).format(amount); 93 | 94 | return
{formatted}
; 95 | }, 96 | }, 97 | { 98 | accessorKey: 'invoice_sent_date', 99 | header: ({ column }) => { 100 | return ( 101 |
column.toggleSorting()} 104 | > 105 | Invoice Sent Date 106 | {column.getIsSorted() === 'asc' ? ( 107 | 108 | ) : column.getIsSorted() === 'desc' ? ( 109 | 110 | ) : ( 111 | 112 | )} 113 |
114 | ); 115 | }, 116 | }, 117 | { 118 | accessorKey: 'invoice_due_date', 119 | header: ({ column }) => { 120 | return ( 121 |
column.toggleSorting()} 124 | > 125 | Invoice Due Date 126 | {column.getIsSorted() === 'asc' ? ( 127 | 128 | ) : column.getIsSorted() === 'desc' ? ( 129 | 130 | ) : ( 131 | 132 | )} 133 |
134 | ); 135 | }, 136 | }, 137 | { 138 | accessorKey: 'payment_date', 139 | header: ({ column }) => { 140 | return ( 141 |
column.toggleSorting()} 144 | > 145 | Payment Date 146 | {column.getIsSorted() === 'asc' ? ( 147 | 148 | ) : column.getIsSorted() === 'desc' ? ( 149 | 150 | ) : ( 151 | 152 | )} 153 |
154 | ); 155 | }, 156 | }, 157 | { 158 | accessorKey: 'payment_method', 159 | header: 'Payment Method', 160 | }, 161 | ]; 162 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/DataTable.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { 4 | ColumnDef, 5 | ColumnFiltersState, 6 | PaginationState, 7 | SortingState, 8 | flexRender, 9 | getCoreRowModel, 10 | getFilteredRowModel, 11 | getPaginationRowModel, 12 | getSortedRowModel, 13 | useReactTable, 14 | } from '@tanstack/react-table'; 15 | 16 | import { 17 | Table, 18 | TableBody, 19 | TableCell, 20 | TableHead, 21 | TableHeader, 22 | TableRow, 23 | } from '@/components/ui/table'; 24 | import { Button } from '@/components/ui/button'; 25 | import { Input } from '@/components/ui/input'; 26 | import { useMemo, useState } from 'react'; 27 | 28 | type DataTableProps = { 29 | columns: ColumnDef[]; 30 | data: TData[]; 31 | }; 32 | 33 | export default function DataTable({ 34 | columns, 35 | data, 36 | }: DataTableProps) { 37 | const [sorting, setSorting] = useState([]); 38 | const [columnFilters, setColumnFilters] = useState([]); 39 | 40 | const [{ pageIndex, pageSize }, setPagination] = useState({ 41 | pageIndex: 0, 42 | pageSize: 8, 43 | }); 44 | 45 | const pagination = useMemo( 46 | () => ({ 47 | pageIndex, 48 | pageSize, 49 | }), 50 | [pageIndex, pageSize] 51 | ); 52 | 53 | const table = useReactTable({ 54 | data, 55 | columns, 56 | onPaginationChange: setPagination, 57 | getCoreRowModel: getCoreRowModel(), 58 | onSortingChange: setSorting, 59 | getPaginationRowModel: getPaginationRowModel(), 60 | getSortedRowModel: getSortedRowModel(), 61 | onColumnFiltersChange: setColumnFilters, 62 | getFilteredRowModel: getFilteredRowModel(), 63 | state: { 64 | sorting, 65 | columnFilters, 66 | pagination, 67 | }, 68 | }); 69 | 70 | return ( 71 | <> 72 |
73 |
74 | 80 | table.getColumn('invoice_id')?.setFilterValue(event.target.value) 81 | } 82 | className='max-w-sm' 83 | /> 84 |
85 | 86 |
87 | 94 | table 95 | .getColumn('account_name') 96 | ?.setFilterValue(event.target.value) 97 | } 98 | className='max-w-sm' 99 | /> 100 |
101 | 102 | 110 |
111 |
112 | 113 | 114 | {table.getHeaderGroups().map((headerGroup) => ( 115 | 116 | {headerGroup.headers.map((header) => { 117 | return ( 118 | 119 | {header.isPlaceholder 120 | ? null 121 | : flexRender( 122 | header.column.columnDef.header, 123 | header.getContext() 124 | )} 125 | 126 | ); 127 | })} 128 | 129 | ))} 130 | 131 | 132 | {table.getRowModel().rows?.length ? ( 133 | table.getRowModel().rows.map((row) => ( 134 | 138 | {row.getVisibleCells().map((cell) => ( 139 | 140 | {flexRender( 141 | cell.column.columnDef.cell, 142 | cell.getContext() 143 | )} 144 | 145 | ))} 146 | 147 | )) 148 | ) : ( 149 | 150 | 154 | No results. 155 | 156 | 157 | )} 158 | 159 |
160 |
161 |
162 | 169 | 176 | 177 |
Page
178 | 179 | {table.getState().pagination.pageIndex + 1} of{' '} 180 | {table.getPageCount()} 181 | 182 |
183 |
184 | 185 | ); 186 | } 187 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function Footer() { 4 | return ( 5 |
6 | 7 | © 2023 PLTG. All rights reserved. 8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/InvoiceDisplay.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Stripe from 'stripe'; 3 | import InvoiceSection from '@/components/InvoiceSection'; 4 | import { 5 | Card, 6 | CardContent, 7 | CardDescription, 8 | CardFooter, 9 | CardHeader, 10 | CardTitle, 11 | } from '@/components/ui/card'; 12 | import { Button } from '@/components/ui/button'; 13 | import { Badge } from '@/components/ui/badge'; 14 | import { AlertCircle } from 'lucide-react'; 15 | 16 | export default function InvoiceDisplay({ 17 | invoice, 18 | error, 19 | }: { 20 | invoice: Stripe.Response | undefined; 21 | error?: string; 22 | }) { 23 | let invoiceDate: string, dueDate: string; 24 | 25 | if (invoice) { 26 | const created = new Date(invoice.created * 1000); 27 | invoiceDate = created.toLocaleString(undefined, { 28 | month: 'short', 29 | day: 'numeric', 30 | year: 'numeric', 31 | }); 32 | 33 | const due = new Date((invoice.due_date as number) * 1000); 34 | dueDate = due.toLocaleString(undefined, { 35 | month: 'short', 36 | day: 'numeric', 37 | year: 'numeric', 38 | }); 39 | } 40 | return invoice && !error ? ( 41 |
42 |

43 | Invoice 44 | {invoice.status === 'open' ? ( 45 | 46 | {invoice.status} 47 | 48 | ) : invoice.status === 'paid' ? ( 49 | 50 | {invoice.status} 51 | 52 | ) : ( 53 | 54 | {invoice.status} 55 | 56 | )} 57 |

58 | 59 | 60 | 61 | #{invoice.number} 62 | 63 | 64 | 65 | Executive Service Corps{' '} 66 | 67 | 1000 Alameda St 68 | 69 | 70 | Los Angeles, CA 90012 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 |
79 | 83 | 84 |
85 |
86 | 90 | {invoice.customer_address && ( 91 |
92 | {invoice.customer_address.line1} 93 | {invoice.customer_address.line2} 94 | 95 | {invoice.customer_address.city},{' '} 96 | {invoice.customer_address.state}{' '} 97 | {invoice.customer_address.postal_code} 98 | 99 |
100 | )} 101 |
102 | 106 |
107 |
108 | 109 | 110 |
111 |
112 | 116 |
117 |
118 | 123 | 133 | 143 |
144 |
145 |
146 |
147 |
148 | 149 |
150 | {invoice.status === 'open' ? ( 151 | 154 | ) : ( 155 | 158 | )} 159 | 165 |
166 |
167 |
168 |
169 | ) : ( 170 |
171 | 172 | 173 | 174 | 175 | 176 | Error 177 | 178 | 179 | Invalid payment link. Please check the link and try again. 180 | 181 | 182 | 183 | 184 |
185 | ); 186 | } 187 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/InvoiceSection.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { cn } from '@/lib/utils'; 3 | 4 | type InvoiceSectionProps = { 5 | title: string; 6 | invoiceData: string | number | null; 7 | variant?: boolean; 8 | }; 9 | 10 | function InvoiceSection({ title, invoiceData, variant }: InvoiceSectionProps) { 11 | return ( 12 |
13 |

{title}

14 |

{invoiceData}

15 |
16 | ); 17 | } 18 | 19 | export default InvoiceSection; 20 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link'; 3 | import Image from 'next/image'; 4 | import Profile from './Profile'; 5 | import { auth } from '@clerk/nextjs'; 6 | 7 | const Navbar = async () => { 8 | const user = auth(); 9 | return ( 10 |
11 |
12 | 13 | Billbot Baggins logo 22 | 23 |
24 | {user ? : null} 25 |
26 | ); 27 | }; 28 | 29 | export default Navbar; 30 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/ParticleEffect.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useCallback } from 'react'; 4 | import type { Container, Engine } from 'tsparticles-engine'; 5 | import Particles from 'react-particles'; 6 | //import { loadFull } from "tsparticles"; // if you are going to use `loadFull`, install the "tsparticles" package too. 7 | import { loadSlim } from 'tsparticles-slim'; // if you are going to use `loadSlim`, install the "tsparticles-slim" package too. 8 | 9 | const ParticleEffect = () => { 10 | const particlesInit = useCallback(async (engine: Engine) => { 11 | console.log(engine); 12 | 13 | // you can initialize the tsParticles instance (engine) here, adding custom shapes or presets 14 | // this loads the tsparticles package bundle, it's the easiest method for getting everything ready 15 | // starting from v2 you can add only the features you need reducing the bundle size 16 | //await loadFull(engine); 17 | await loadSlim(engine); 18 | }, []); 19 | 20 | const particlesLoaded = useCallback( 21 | async (container: Container | undefined) => { 22 | await console.log(container); 23 | }, 24 | [] 25 | ); 26 | return ( 27 | 101 | ); 102 | }; 103 | 104 | export default ParticleEffect; 105 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/Profile.tsx: -------------------------------------------------------------------------------- 1 | import { UserButton } from '@clerk/nextjs'; 2 | import React from 'react'; 3 | 4 | function Profile() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | 12 | export default Profile; 13 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/RecentSales.tsx: -------------------------------------------------------------------------------- 1 | import { PaymentProps } from '@/lib/types'; 2 | 3 | export default function RecentSales({ 4 | payments, 5 | }: { 6 | payments: PaymentProps[]; 7 | }) { 8 | return ( 9 |
10 | {payments.map((payment) => ( 11 |
12 |
13 |

14 | {payment.account_name} 15 |

16 |

17 | ESC-{payment.invoice_id} 18 |

19 |
20 |
21 | + 22 | {payment.amount.toLocaleString('en-US', { 23 | style: 'currency', 24 | currency: 'USD', 25 | })} 26 |
27 |
28 | ))} 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/TeamMember.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | import Link from 'next/link'; 3 | import React from 'react'; 4 | 5 | type TeamMemberProps = { 6 | href: string; 7 | email: string; 8 | firstName: string; 9 | lastName: string; 10 | }; 11 | 12 | const TeamMember = ({ href, email, firstName, lastName }: TeamMemberProps) => { 13 | return ( 14 |
15 | 16 | GitHub Invertocat logo 23 | 24 | 25 |

26 | {firstName}{' '} 27 | {lastName}{' '} 28 |

29 | 30 |
31 | ); 32 | }; 33 | 34 | export default TeamMember; 35 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/index.ts: -------------------------------------------------------------------------------- 1 | import Navbar from '@/components/Navbar'; 2 | import Footer from '@/components/Footer'; 3 | import Overview from '@/components/overview'; 4 | import RecentSales from '@/components/RecentSales'; 5 | import DataTable from '@/components/DataTable'; 6 | import { columns }from '@/components/Columns'; 7 | 8 | 9 | export { 10 | Navbar, 11 | Footer, 12 | Overview, 13 | RecentSales, 14 | DataTable, 15 | columns, 16 | }; -------------------------------------------------------------------------------- /bill-bot-baggins/components/overview.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { useEffect, useState } from 'react'; 4 | import { 5 | Bar, 6 | BarChart, 7 | ResponsiveContainer, 8 | Tooltip, 9 | TooltipProps, 10 | XAxis, 11 | YAxis, 12 | } from 'recharts'; 13 | 14 | import type { 15 | ValueType, 16 | NameType, 17 | } from 'recharts/types/component/DefaultTooltipContent'; 18 | 19 | const CustomTooltip = ({ 20 | active, 21 | payload, 22 | label, 23 | }: TooltipProps) => { 24 | if (active && payload && payload.length) { 25 | return ( 26 |
27 |

{`Revenue`}

28 |

{`${label}: ${payload[0].value?.toLocaleString( 29 | 'en-US', 30 | { 31 | style: 'currency', 32 | currency: 'USD', 33 | } 34 | )}`}

35 |
36 | ); 37 | } 38 | 39 | return null; 40 | }; 41 | 42 | type OverviewProps = { 43 | data: { 44 | month: string; 45 | revenue: number; 46 | }[]; 47 | }; 48 | 49 | const Overview = ({ data }: OverviewProps) => { 50 | const [isLoading, setIsLoading] = useState(true); 51 | 52 | useEffect(() => { 53 | setIsLoading(false); 54 | }, [data]); 55 | 56 | return !isLoading ? ( 57 | 58 | 59 | 66 | `$${value}`} 72 | /> 73 | } /> 74 | 75 | 76 | 77 | ) : ( 78 |
79 |
83 | 84 | Loading... 85 | 86 |
87 |
88 | ); 89 | }; 90 | 91 | export default Overview; 92 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/ui/alert-dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" 5 | 6 | import { cn } from "@/lib/utils" 7 | import { buttonVariants } from "@/components/ui/button" 8 | 9 | const AlertDialog = AlertDialogPrimitive.Root 10 | 11 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger 12 | 13 | const AlertDialogPortal = AlertDialogPrimitive.Portal 14 | 15 | const AlertDialogOverlay = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, children, ...props }, ref) => ( 19 | 27 | )) 28 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName 29 | 30 | const AlertDialogContent = React.forwardRef< 31 | React.ElementRef, 32 | React.ComponentPropsWithoutRef 33 | >(({ className, ...props }, ref) => ( 34 | 35 | 36 | 44 | 45 | )) 46 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName 47 | 48 | const AlertDialogHeader = ({ 49 | className, 50 | ...props 51 | }: React.HTMLAttributes) => ( 52 |
59 | ) 60 | AlertDialogHeader.displayName = "AlertDialogHeader" 61 | 62 | const AlertDialogFooter = ({ 63 | className, 64 | ...props 65 | }: React.HTMLAttributes) => ( 66 |
73 | ) 74 | AlertDialogFooter.displayName = "AlertDialogFooter" 75 | 76 | const AlertDialogTitle = React.forwardRef< 77 | React.ElementRef, 78 | React.ComponentPropsWithoutRef 79 | >(({ className, ...props }, ref) => ( 80 | 85 | )) 86 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName 87 | 88 | const AlertDialogDescription = React.forwardRef< 89 | React.ElementRef, 90 | React.ComponentPropsWithoutRef 91 | >(({ className, ...props }, ref) => ( 92 | 97 | )) 98 | AlertDialogDescription.displayName = 99 | AlertDialogPrimitive.Description.displayName 100 | 101 | const AlertDialogAction = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )) 111 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName 112 | 113 | const AlertDialogCancel = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, ...props }, ref) => ( 117 | 126 | )) 127 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName 128 | 129 | export { 130 | AlertDialog, 131 | AlertDialogPortal, 132 | AlertDialogOverlay, 133 | AlertDialogTrigger, 134 | AlertDialogContent, 135 | AlertDialogHeader, 136 | AlertDialogFooter, 137 | AlertDialogTitle, 138 | AlertDialogDescription, 139 | AlertDialogAction, 140 | AlertDialogCancel, 141 | } 142 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-background text-foreground", 12 | destructive: 13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: "default", 18 | }, 19 | } 20 | ) 21 | 22 | const Alert = React.forwardRef< 23 | HTMLDivElement, 24 | React.HTMLAttributes & VariantProps 25 | >(({ className, variant, ...props }, ref) => ( 26 |
32 | )) 33 | Alert.displayName = "Alert" 34 | 35 | const AlertTitle = React.forwardRef< 36 | HTMLParagraphElement, 37 | React.HTMLAttributes 38 | >(({ className, ...props }, ref) => ( 39 |
44 | )) 45 | AlertTitle.displayName = "AlertTitle" 46 | 47 | const AlertDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )) 57 | AlertDescription.displayName = "AlertDescription" 58 | 59 | export { Alert, AlertTitle, AlertDescription } 60 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { cva, type VariantProps } from 'class-variance-authority'; 3 | 4 | import { cn } from '@/lib/utils'; 5 | 6 | const badgeVariants = cva( 7 | 'inline-flex items-center rounded-full border px-3.5 py-1.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2', 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80', 13 | secondary: 14 | 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80', 15 | destructive: 16 | 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80', 17 | outline: 'text-foreground', 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: 'default', 22 | }, 23 | } 24 | ); 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ); 34 | } 35 | 36 | export { Badge, badgeVariants }; 37 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Slot } from '@radix-ui/react-slot'; 3 | import { cva, type VariantProps } from 'class-variance-authority'; 4 | 5 | import { cn } from '@/lib/utils'; 6 | 7 | const buttonVariants = cva( 8 | 'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', 9 | { 10 | variants: { 11 | variant: { 12 | default: 'bg-primary text-primary-foreground hover:bg-primary/90', 13 | destructive: 14 | 'bg-destructive text-destructive-foreground hover:bg-destructive/90', 15 | outline: 16 | 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', 17 | secondary: 18 | 'bg-secondary text-secondary-foreground hover:bg-secondary/80', 19 | ghost: 'hover:bg-accent hover:text-accent-foreground', 20 | link: 'text-primary underline-offset-4 hover:underline', 21 | indigo: 22 | 'bg-indigo-600 hover:bg-indigo-600/90 w-full transition-all active:scale-95', 23 | }, 24 | size: { 25 | default: 'h-10 px-4 py-2', 26 | sm: 'h-9 rounded-md px-3', 27 | lg: 'h-11 rounded-md px-8', 28 | icon: 'h-10 w-10', 29 | }, 30 | }, 31 | defaultVariants: { 32 | variant: 'default', 33 | size: 'default', 34 | }, 35 | } 36 | ); 37 | 38 | export interface ButtonProps 39 | extends React.ButtonHTMLAttributes, 40 | VariantProps { 41 | asChild?: boolean; 42 | } 43 | 44 | const Button = React.forwardRef( 45 | ({ className, variant, size, asChild = false, ...props }, ref) => { 46 | const Comp = asChild ? Slot : 'button'; 47 | return ( 48 | 53 | ); 54 | } 55 | ); 56 | Button.displayName = 'Button'; 57 | 58 | export { Button, buttonVariants }; 59 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

44 | )) 45 | CardTitle.displayName = "CardTitle" 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

56 | )) 57 | CardDescription.displayName = "CardDescription" 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

64 | )) 65 | CardContent.displayName = "CardContent" 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )) 77 | CardFooter.displayName = "CardFooter" 78 | 79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 80 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 5 | import { Check, ChevronRight, Circle } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const DropdownMenu = DropdownMenuPrimitive.Root 10 | 11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger 12 | 13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group 14 | 15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal 16 | 17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub 18 | 19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup 20 | 21 | const DropdownMenuSubTrigger = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef & { 24 | inset?: boolean 25 | } 26 | >(({ className, inset, children, ...props }, ref) => ( 27 | 36 | {children} 37 | 38 | 39 | )) 40 | DropdownMenuSubTrigger.displayName = 41 | DropdownMenuPrimitive.SubTrigger.displayName 42 | 43 | const DropdownMenuSubContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, ...props }, ref) => ( 47 | 55 | )) 56 | DropdownMenuSubContent.displayName = 57 | DropdownMenuPrimitive.SubContent.displayName 58 | 59 | const DropdownMenuContent = React.forwardRef< 60 | React.ElementRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, sideOffset = 4, ...props }, ref) => ( 63 | 64 | 73 | 74 | )) 75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName 76 | 77 | const DropdownMenuItem = React.forwardRef< 78 | React.ElementRef, 79 | React.ComponentPropsWithoutRef & { 80 | inset?: boolean 81 | } 82 | >(({ className, inset, ...props }, ref) => ( 83 | 92 | )) 93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName 94 | 95 | const DropdownMenuCheckboxItem = React.forwardRef< 96 | React.ElementRef, 97 | React.ComponentPropsWithoutRef 98 | >(({ className, children, checked, ...props }, ref) => ( 99 | 108 | 109 | 110 | 111 | 112 | 113 | {children} 114 | 115 | )) 116 | DropdownMenuCheckboxItem.displayName = 117 | DropdownMenuPrimitive.CheckboxItem.displayName 118 | 119 | const DropdownMenuRadioItem = React.forwardRef< 120 | React.ElementRef, 121 | React.ComponentPropsWithoutRef 122 | >(({ className, children, ...props }, ref) => ( 123 | 131 | 132 | 133 | 134 | 135 | 136 | {children} 137 | 138 | )) 139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName 140 | 141 | const DropdownMenuLabel = React.forwardRef< 142 | React.ElementRef, 143 | React.ComponentPropsWithoutRef & { 144 | inset?: boolean 145 | } 146 | >(({ className, inset, ...props }, ref) => ( 147 | 156 | )) 157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName 158 | 159 | const DropdownMenuSeparator = React.forwardRef< 160 | React.ElementRef, 161 | React.ComponentPropsWithoutRef 162 | >(({ className, ...props }, ref) => ( 163 | 168 | )) 169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName 170 | 171 | const DropdownMenuShortcut = ({ 172 | className, 173 | ...props 174 | }: React.HTMLAttributes) => { 175 | return ( 176 | 180 | ) 181 | } 182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut" 183 | 184 | export { 185 | DropdownMenu, 186 | DropdownMenuTrigger, 187 | DropdownMenuContent, 188 | DropdownMenuItem, 189 | DropdownMenuCheckboxItem, 190 | DropdownMenuRadioItem, 191 | DropdownMenuLabel, 192 | DropdownMenuSeparator, 193 | DropdownMenuShortcut, 194 | DropdownMenuGroup, 195 | DropdownMenuPortal, 196 | DropdownMenuSub, 197 | DropdownMenuSubContent, 198 | DropdownMenuSubTrigger, 199 | DropdownMenuRadioGroup, 200 | } 201 | -------------------------------------------------------------------------------- /bill-bot-baggins/components/ui/form.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | import { Slot } from "@radix-ui/react-slot" 4 | import { 5 | Controller, 6 | ControllerProps, 7 | FieldPath, 8 | FieldValues, 9 | FormProvider, 10 | useFormContext, 11 | } from "react-hook-form" 12 | 13 | import { cn } from "@/lib/utils" 14 | import { Label } from "@/components/ui/label" 15 | 16 | const Form = FormProvider 17 | 18 | type FormFieldContextValue< 19 | TFieldValues extends FieldValues = FieldValues, 20 | TName extends FieldPath = FieldPath 21 | > = { 22 | name: TName 23 | } 24 | 25 | const FormFieldContext = React.createContext( 26 | {} as FormFieldContextValue 27 | ) 28 | 29 | const FormField = < 30 | TFieldValues extends FieldValues = FieldValues, 31 | TName extends FieldPath = FieldPath 32 | >({ 33 | ...props 34 | }: ControllerProps) => { 35 | return ( 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | const useFormField = () => { 43 | const fieldContext = React.useContext(FormFieldContext) 44 | const itemContext = React.useContext(FormItemContext) 45 | const { getFieldState, formState } = useFormContext() 46 | 47 | const fieldState = getFieldState(fieldContext.name, formState) 48 | 49 | if (!fieldContext) { 50 | throw new Error("useFormField should be used within ") 51 | } 52 | 53 | const { id } = itemContext 54 | 55 | return { 56 | id, 57 | name: fieldContext.name, 58 | formItemId: `${id}-form-item`, 59 | formDescriptionId: `${id}-form-item-description`, 60 | formMessageId: `${id}-form-item-message`, 61 | ...fieldState, 62 | } 63 | } 64 | 65 | type FormItemContextValue = { 66 | id: string 67 | } 68 | 69 | const FormItemContext = React.createContext( 70 | {} as FormItemContextValue 71 | ) 72 | 73 | const FormItem = React.forwardRef< 74 | HTMLDivElement, 75 | React.HTMLAttributes 76 | >(({ className, ...props }, ref) => { 77 | const id = React.useId() 78 | 79 | return ( 80 | 81 |
82 | 83 | ) 84 | }) 85 | FormItem.displayName = "FormItem" 86 | 87 | const FormLabel = React.forwardRef< 88 | React.ElementRef, 89 | React.ComponentPropsWithoutRef 90 | >(({ className, ...props }, ref) => { 91 | const { error, formItemId } = useFormField() 92 | 93 | return ( 94 |