├── .env.local.example ├── .eslintrc ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── .npmrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── app ├── api │ ├── chatAPI │ │ └── route.ts │ ├── essayAPI │ │ └── route.ts │ └── webhooks │ │ └── route.ts ├── auth │ ├── callback │ │ └── route.ts │ └── reset_password │ │ └── route.ts ├── dashboard │ ├── ai-chat │ │ └── page.tsx │ ├── main │ │ └── page.tsx │ ├── page.tsx │ ├── settings │ │ └── page.tsx │ └── signin │ │ ├── [id] │ │ └── page.tsx │ │ └── page.tsx ├── layout.tsx ├── page.tsx ├── supabase-provider.tsx ├── supabase-server.ts └── theme-provider.tsx ├── components.json ├── components ├── MessageBox.tsx ├── MessageBoxChat.tsx ├── TextBlock.tsx ├── auth-ui │ ├── EmailSignIn.tsx │ ├── ForgotPassword.tsx │ ├── OauthSignIn.tsx │ ├── PasswordSignIn.tsx │ ├── Separator.tsx │ ├── Signup.tsx │ └── UpdatePassword.tsx ├── auth │ ├── AuthUI.tsx │ └── index.tsx ├── card │ └── CardMenu.tsx ├── charts │ └── LineChart │ │ └── index.tsx ├── dashboard │ ├── ai-chat │ │ └── index.tsx │ ├── main │ │ ├── cards │ │ │ ├── MainChart.tsx │ │ │ └── MainDashboardTable.tsx │ │ └── index.tsx │ └── settings │ │ ├── components │ │ └── notification-settings.tsx │ │ └── index.tsx ├── footer │ ├── FooterAdmin.tsx │ └── FooterAuthDefault.tsx ├── hooks │ └── use-toast.ts ├── layout │ ├── index.tsx │ └── innerContent.tsx ├── link │ └── NavLink.tsx ├── navbar │ ├── NavbarAdmin.tsx │ └── NavbarLinksAdmin.tsx ├── notification │ └── index.tsx ├── routes.tsx ├── scrollbar │ └── Scrollbar.tsx └── sidebar │ ├── Sidebar.tsx │ └── components │ ├── Links.tsx │ └── SidebarCard.tsx ├── contexts └── layout.ts ├── fixtures └── stripe-fixtures.json ├── hooks ├── use-mobile.tsx └── use-toast.ts ├── jsconfig.json ├── lib └── utils.ts ├── middleware.ts ├── next.config.js ├── package.json ├── postcss.config.js ├── prettier.config.js ├── public ├── Modal.png ├── SidebarBadge.png ├── img │ ├── dark │ │ └── ai-chat │ │ │ └── bg-image.png │ ├── favicon.ico │ └── light │ │ └── ai-chat │ │ └── bg-image.png └── robots.txt ├── schema.sql ├── styles ├── chrome-bug.css ├── globals.css └── output.css ├── supabase ├── .gitignore └── config.toml.example ├── tailwind.config.ts ├── tsconfig.json ├── types ├── supabase.ts ├── types.ts └── types_db.ts ├── utils ├── auth-helpers │ ├── client.ts │ ├── server.ts │ └── settings.ts ├── chatStream.ts ├── cn.ts ├── cookies.ts ├── helpers.ts ├── navigation.tsx ├── streams │ ├── chatStream.ts │ └── essayStream.ts ├── stripe │ ├── client.ts │ └── config.ts ├── supabase-admin.ts └── supabase │ ├── admin.ts │ ├── client.ts │ ├── middleware.ts │ ├── queries.ts │ └── server.ts └── variables ├── charts.ts ├── tableDataInvoice.ts └── tableDataUserReports.ts /.env.local.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_SUPABASE_URL=https://********************.supabase.co 2 | NEXT_PUBLIC_SUPABASE_ANON_KEY=**************************************************************************************************************************************************************************************************************** 3 | 4 | NEXT_PUBLIC_OPENAI_API_KEY=sk-************************************************ 5 | NEXT_PUBLIC_OPENAI_ASSISTANT_KEY=asst_************************ 6 | 7 | # Update these with your Supabase details from your project settings > API 8 | SUPABASE_SERVICE_ROLE_KEY=*************************************************************************************************************************************************************************************************************************** 9 | 10 | # Update these with your Stripe credentials from https://dashboard.stripe.com/apikeys 11 | # NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_*************************************************************************************************** 12 | NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_*************************************************************************************************** 13 | # STRIPE_SECRET_KEY=sk_live_*************************************************************************************************** 14 | STRIPE_SECRET_KEY=sk_test_*************************************************************************************************** 15 | # The commented variable is usually for production webhook key. This you get in the Stripe dashboard and is usually shorter. 16 | # STRIPE_WEBHOOK_SECRET=whsec_******************************** 17 | STRIPE_WEBHOOK_SECRET=whsec_**************************************************************** 18 | 19 | # Update this with your stable site URL only for the production environment. 20 | # NEXT_PUBLIC_SITE_URL=https://horizon-ui.com/shadcn-nextjs-boilerplate 21 | # NEXT_PUBLIC_SITE_URL=https://******************.com 22 | 23 | NEXT_PUBLIC_AWS_S3_REGION=eu-north-1 24 | NEXT_PUBLIC_AWS_S3_ACCESS_KEY_ID=******************** 25 | NEXT_PUBLIC_AWS_S3_SECRET_ACCESS_KEY=**************************************** 26 | NEXT_PUBLIC_AWS_S3_BUCKET_NAME=mybucket -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/babel","next/core-web-vitals"] 3 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | .supabase/seed.sql 22 | supabase/seed.sql 23 | supabase/migrations 24 | 25 | # debug 26 | npm-debug.log* 27 | yarn-debug.log* 28 | yarn-error.log* 29 | .pnpm-debug.log* 30 | 31 | # local env files 32 | .env 33 | .env.local 34 | .env.development.local 35 | .env.test.local 36 | .env.production.local 37 | 38 | # vercel 39 | .vercel 40 | 41 | # typescript 42 | *.tsbuildinfo 43 | next-env.d.ts 44 | 45 | package-lock.json 46 | .env.local.production 47 | .env.local.new 48 | .env.local.non-docker 49 | 50 | components/ui 51 | 52 | yarn.lock 53 | 54 | # editors 55 | .vscode -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/horizon-ui/shadcn-nextjs-boilerplate/efe90c62391f2d3247a5a5f0712adcad0515aba7/.npmignore -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | auto-install-peers=true 3 | strict-peer-dependencies=false -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.0.0] 2025-27-01 2 | 3 | ### Upgraded to React 19 ⚡️ 4 | 5 | ### Upgraded to Next.js 15 ⚡️ 6 | 7 | ## [2.1.1] 2024-09-10 8 | 9 | ### Signout fixed ⚡️ 10 | - Fixed a bug where you couldn't sign out or change name in Supabase. 11 | 12 | ## [2.1.0] 2024-09-05 13 | 14 | ### Added Profile Settings page. ⚡️ 15 | - Added a new Profile Settings page. 16 | 17 | ## [2.0.0] 2024-08-26 18 | 19 | ### Big update: Supabase SSR, Refactoring & custom auth components 20 | 21 | - #### Supabase SSR 22 | - Utils ⁠ folder refactored, now functions are organized in separate folders based on usage 23 | - ⁠New auth-related utils 24 | - ⁠Functions like ⁠ getSessions ⁠ were removed because of the use of Supabase SSR 25 | - session ⁠ object was replaced with ⁠ user ⁠ throughout the project 26 | 27 | - #### Layout refactoring 28 | - ⁠The multiple addition of functionalities led to prop drilling, which was fixed by using contexts. 29 | 30 | - #### Separate auth pages 31 | - ⁠Auth pages are dynamic Next.js pages, one for each of Update password, sign up, password sign in, etc. 32 | - ⁠The forms for each type of authentication types are located in ⁠ @/components/auth-ui  33 | 34 | - #### Added Docker support 35 | - You can now develop locally via Docker by using Supabase CLI 36 | 37 | ## [1.1.0] 2024-07-18 38 | 39 | ### Added Main Dashboard Page 40 | 41 | - Added Main Dashboard Page 42 | 43 | ## [1.0.0] 2024-05-20 44 | 45 | ### Initial Release 46 | 47 | - Added Shadcn UI as base framework 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Horizon UI 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |   2 |

3 | 4 | Horizon AI Boilerplate Admin Dashboard NextJS Shadcn UI 5 | 6 |

7 | 8 |

9 | Website • 10 | Documentation • 11 | PRO Version • 12 | Free Template • 13 | Twitter 14 |

15 | Launch your SaaS startup within a few days with the first Admin Dashboard Shadcn UI NextJS boilerplate. Get started with Horizon AI Boilerplate today! 16 | 17 |

18 | 19 |   20 | 21 |

22 | Horizon AI Boilerplate NextJS Shadcn UI 23 |

24 | 25 | 26 |   27 | 28 | ### Introduction 29 | 30 | Horizon AI Boilerplate is the first open-source Admin Dashboard OpenAI ChatGPT AI Template made for Shadcn UI, NextJS, and Tailwind CSS! Start creating outstanding Chat AI SaaS Apps faster. 31 | 32 | It comes with over 30+ dark/light frontend individual elements, like buttons, inputs, navbars, nav tabs, cards, or alerts, giving you the freedom of choosing and combining. 33 | 34 | ### Documentation 35 | 36 | Each element is well presented in a very complex documentation. You can read more about the documentation here. 37 | 38 | ### Quick Start 39 | 40 | Install Horizon ChatGPT AI Template by running either of the following: 41 | 42 | - Install NodeJS LTS from [NodeJs Official Page](https://nodejs.org/en/?ref=horizon-documentation) (NOTE: Product only works with LTS version) 43 | 44 |
45 | 46 | Clone the repository with the following command: 47 | 48 | ```bash 49 | git clone https://github.com/horizon-ui/shadcn-nextjs-boilerplate.git 50 | ``` 51 | 52 | Run in the terminal this command: 53 | 54 | ``` 55 | npm install 56 | ``` 57 | 58 |
59 | 60 | ``` 61 | npm run init 62 | ``` 63 | 64 |
65 | 66 | Then run this command to start your local server: 67 | 68 | ``` 69 | npm run dev 70 | ``` 71 |   72 | 73 | ### Your API Key is not working? 74 | 75 | - Make sure you have an [OpenAI account](https://platform.openai.com/account) and a valid API key to use ChatGPT. We don't sell API keys. 76 | - Make sure you have your billing info added in [OpenAI Billing page](https://platform.openai.com/account/billing/overview). Without billing info, your API key will not work. 77 | - The app will connect to the OpenAI API server to check if your API Key is working properly. 78 | 79 | 80 | ### Figma Version 81 | 82 | Horizon AI Boilerplate is available in Figma format as well! [Check it out here](https://www.figma.com/community/file/1374394029061088369)! 🎨 83 | 84 | 85 | ### Example Sections 86 | 87 | If you want to get inspiration for your startup project or just show something directly to your clients, you can jump-start your development with our pre-built example sections. You will be able to quickly set up the basic structure for your web project. 88 | 89 | View example sections here 90 | 91 | --- 92 | 93 | 94 | # PRO Version 95 | 96 | Unlock a huge amount of components and pages with our PRO version - Learn more 97 | 98 |

99 | Horizon AI Boilerplate NextJS Shadcn UI 100 |

101 | 102 | 103 | --- 104 | 105 | # Reporting Issues 106 | 107 | We use GitHub Issues as the official bug tracker for the Horizon UI. Here are 108 | some advice for our users who want to report an issue: 109 | 110 | 1. Make sure that you are using the latest version of the Horizon UI Boilerplate. Check the CHANGELOG for your dashboard on our [CHANGE LOG File](https://github.com/horizon-ui/shadcn-nextjs-boilerplate/blob/main/CHANGELOG.md). 111 |
112 | 113 | 1. Providing us with reproducible steps for the issue will shorten the time it takes for it to be fixed. 114 |
115 | 116 | 117 | 3. Some issues may be browser-specific, so specifying in what browser you encountered the issue might help. 118 | 119 | --- 120 | 121 | # Community 122 | 123 | Connect with the community! Feel free to ask questions, report issues, and meet new people who already use Horizon UI! 124 | 125 | 💬 [Join the #HorizonUI Discord Community!](https://discord.gg/f6tEKFBd4m) 126 | 127 | 128 | ### Copyright and license 129 | 130 | ⭐️ [Copyright 2024 Horizon UI](https://www.horizon-ui.com/?ref=readme-horizon) 131 | 132 | 📄 [Horizon UI License](https://horizon-ui.notion.site/End-User-License-Agreement-8fb09441ea8c4c08b60c37996195a6d5) 133 | 134 | 135 | --- 136 | 137 | # Credits 138 | 139 | Special thanks to the open-source resources that helped us create this awesome boilerplate package, including: 140 | 141 | - [Shadcn UI Library](https://ui.shadcn.com/) 142 | - [NextJS Subscription Payments](https://github.com/vercel/nextjs-subscription-payments) 143 | - [ChatBot UI by mckaywrigley](https://github.com/mckaywrigley/chatbot-ui) 144 | -------------------------------------------------------------------------------- /app/api/chatAPI/route.ts: -------------------------------------------------------------------------------- 1 | import { ChatBody } from '@/types/types'; 2 | import { OpenAIStream } from '@/utils/streams/chatStream'; 3 | 4 | export const runtime = 'edge'; 5 | 6 | export async function GET(req: Request): Promise { 7 | try { 8 | const { inputMessage, model, apiKey } = (await req.json()) as ChatBody; 9 | 10 | let apiKeyFinal; 11 | if (apiKey) { 12 | apiKeyFinal = apiKey; 13 | } else { 14 | apiKeyFinal = process.env.NEXT_PUBLIC_OPENAI_API_KEY; 15 | } 16 | 17 | const stream = await OpenAIStream(inputMessage, model, apiKeyFinal); 18 | 19 | return new Response(stream); 20 | } catch (error) { 21 | console.error(error); 22 | return new Response('Error', { status: 500 }); 23 | } 24 | } 25 | export async function POST(req: Request): Promise { 26 | try { 27 | const { inputMessage, model, apiKey } = (await req.json()) as ChatBody; 28 | 29 | let apiKeyFinal; 30 | if (apiKey) { 31 | apiKeyFinal = apiKey; 32 | } else { 33 | apiKeyFinal = process.env.NEXT_PUBLIC_OPENAI_API_KEY; 34 | } 35 | 36 | const stream = await OpenAIStream(inputMessage, model, apiKeyFinal); 37 | 38 | return new Response(stream); 39 | } catch (error) { 40 | console.error(error); 41 | return new Response('Error', { status: 500 }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/api/essayAPI/route.ts: -------------------------------------------------------------------------------- 1 | import { EssayBody } from '@/types/types'; 2 | import { OpenAIStream } from '@/utils/streams/essayStream'; 3 | 4 | export const runtime = 'edge'; 5 | 6 | export async function GET(req: Request): Promise { 7 | try { 8 | const { 9 | topic, 10 | words, 11 | essayType, 12 | model, 13 | apiKey 14 | } = (await req.json()) as EssayBody; 15 | 16 | let apiKeyFinal; 17 | if (apiKey) { 18 | apiKeyFinal = apiKey; 19 | } else { 20 | apiKeyFinal = process.env.NEXT_PUBLIC_OPENAI_API_KEY; 21 | } 22 | 23 | const stream = await OpenAIStream( 24 | topic, 25 | essayType, 26 | words, 27 | model, 28 | apiKeyFinal 29 | ); 30 | 31 | return new Response(stream); 32 | } catch (error) { 33 | console.error(error); 34 | return new Response('Error', { status: 500 }); 35 | } 36 | } 37 | export async function POST(req: Request): Promise { 38 | try { 39 | const { 40 | topic, 41 | words, 42 | essayType, 43 | model, 44 | apiKey 45 | } = (await req.json()) as EssayBody; 46 | 47 | let apiKeyFinal; 48 | if (apiKey) { 49 | apiKeyFinal = apiKey; 50 | } else { 51 | apiKeyFinal = process.env.NEXT_PUBLIC_OPENAI_API_KEY; 52 | } 53 | 54 | const stream = await OpenAIStream( 55 | topic, 56 | essayType, 57 | words, 58 | model, 59 | apiKeyFinal 60 | ); 61 | 62 | return new Response(stream); 63 | } catch (error) { 64 | console.error(error); 65 | return new Response('Error', { status: 500 }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/api/webhooks/route.ts: -------------------------------------------------------------------------------- 1 | import Stripe from 'stripe'; 2 | import { stripe } from '@/utils/stripe/config'; 3 | import { 4 | upsertProductRecord, 5 | upsertPriceRecord, 6 | manageSubscriptionStatusChange 7 | } from '@/utils/supabase-admin'; 8 | import { headers } from 'next/headers'; 9 | 10 | const relevantEvents = new Set([ 11 | 'product.created', 12 | 'product.updated', 13 | 'price.created', 14 | 'price.updated', 15 | 'checkout.session.completed', 16 | 'customer.subscription.created', 17 | 'customer.subscription.updated', 18 | 'customer.subscription.deleted' 19 | ]); 20 | 21 | export async function POST(req: Request) { 22 | const body = await req.text(); 23 | const sig = headers().get('Stripe-Signature') as string; 24 | const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; 25 | let event: Stripe.Event; 26 | 27 | try { 28 | if (!sig || !webhookSecret) return; 29 | event = stripe.webhooks.constructEvent(body, sig, webhookSecret); 30 | } catch (err) { 31 | console.log(`❌ Error message: ${err.message}`); 32 | return new Response(`Webhook Error: ${err.message}`, { status: 400 }); 33 | } 34 | 35 | if (relevantEvents.has(event.type)) { 36 | try { 37 | switch (event.type) { 38 | case 'product.created': 39 | case 'product.updated': 40 | await upsertProductRecord(event.data.object as Stripe.Product); 41 | break; 42 | case 'price.created': 43 | case 'price.updated': 44 | await upsertPriceRecord(event.data.object as Stripe.Price); 45 | break; 46 | case 'customer.subscription.created': 47 | case 'customer.subscription.updated': 48 | case 'customer.subscription.deleted': 49 | const subscription = event.data.object as Stripe.Subscription; 50 | await manageSubscriptionStatusChange( 51 | subscription.id, 52 | subscription.customer as string, 53 | event.type === 'customer.subscription.created' 54 | ); 55 | break; 56 | case 'checkout.session.completed': 57 | const checkoutSession = event.data.object as Stripe.Checkout.Session; 58 | if (checkoutSession.mode === 'subscription') { 59 | const subscriptionId = checkoutSession.subscription; 60 | await manageSubscriptionStatusChange( 61 | subscriptionId as string, 62 | checkoutSession.customer as string, 63 | true 64 | ); 65 | } 66 | break; 67 | default: 68 | throw new Error('Unhandled relevant event!'); 69 | } 70 | } catch (error) { 71 | console.log(error); 72 | return new Response( 73 | 'Webhook handler failed. View your nextjs function logs.', 74 | { 75 | status: 400 76 | } 77 | ); 78 | } 79 | } 80 | return new Response(JSON.stringify({ received: true })); 81 | } 82 | -------------------------------------------------------------------------------- /app/auth/callback/route.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from '@/utils/supabase/server'; 2 | import { NextResponse } from 'next/server'; 3 | import { NextRequest } from 'next/server'; 4 | import { getErrorRedirect, getStatusRedirect } from '@/utils/helpers'; 5 | 6 | export async function GET(request: NextRequest) { 7 | // The `/auth/callback` route is required for the server-side auth flow implemented 8 | // by the `@supabase/ssr` package. It exchanges an auth code for the user's session. 9 | const requestUrl = new URL(request.url); 10 | const code = requestUrl.searchParams.get('code'); 11 | 12 | if (code) { 13 | const supabase = createClient(); 14 | 15 | const { error } = await supabase.auth.exchangeCodeForSession(code); 16 | 17 | if (error) { 18 | return NextResponse.redirect( 19 | getErrorRedirect( 20 | `${requestUrl.origin}/dashboard/signin`, 21 | error.name, 22 | "Sorry, we weren't able to log you in. Please try again." 23 | ) 24 | ); 25 | } 26 | } 27 | 28 | // URL to redirect to after sign in process completes 29 | return NextResponse.redirect( 30 | getStatusRedirect( 31 | `${requestUrl.origin}/dashboard/main`, 32 | 'Success!', 33 | 'You are now signed in.' 34 | ) 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /app/auth/reset_password/route.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from '@/utils/supabase/server'; 2 | import { NextResponse } from 'next/server'; 3 | import { NextRequest } from 'next/server'; 4 | import { getErrorRedirect, getStatusRedirect } from '@/utils/helpers'; 5 | 6 | export async function GET(request: NextRequest) { 7 | // The `/auth/callback` route is required for the server-side auth flow implemented 8 | // by the `@supabase/ssr` package. It exchanges an auth code for the user's session. 9 | const requestUrl = new URL(request.url); 10 | const code = requestUrl.searchParams.get('code'); 11 | 12 | if (code) { 13 | const supabase = createClient(); 14 | 15 | const { error } = await supabase.auth.exchangeCodeForSession(code); 16 | 17 | if (error) { 18 | return NextResponse.redirect( 19 | getErrorRedirect( 20 | `${requestUrl.origin}/signin/forgot_password`, 21 | error.name, 22 | "Sorry, we weren't able to log you in. Please try again." 23 | ) 24 | ); 25 | } 26 | } 27 | 28 | // URL to redirect to after sign in process completes 29 | return NextResponse.redirect( 30 | getStatusRedirect( 31 | `${requestUrl.origin}/signin/update_password`, 32 | 'You are now signed in.', 33 | 'Please enter a new password for your account.' 34 | ) 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /app/dashboard/ai-chat/page.tsx: -------------------------------------------------------------------------------- 1 | import { getUserDetails, getUser } from '@/utils/supabase/queries'; 2 | 3 | import Chat from '@/components/dashboard/ai-chat'; 4 | import { redirect } from 'next/navigation'; 5 | import { createClient } from '@/utils/supabase/server'; 6 | 7 | export default async function AiChat() { 8 | const supabase = createClient(); 9 | const [user, userDetails] = await Promise.all([ 10 | getUser(supabase), 11 | getUserDetails(supabase) 12 | ]); 13 | 14 | if (!user) { 15 | return redirect('/dashboard/signin'); 16 | } 17 | 18 | return ; 19 | } 20 | -------------------------------------------------------------------------------- /app/dashboard/main/page.tsx: -------------------------------------------------------------------------------- 1 | import Main from '@/components/dashboard/main'; 2 | import { redirect } from 'next/navigation'; 3 | import { getUserDetails, getUser } from '@/utils/supabase/queries'; 4 | import { createClient } from '@/utils/supabase/server'; 5 | 6 | export default async function Account() { 7 | const supabase = createClient(); 8 | const [user, userDetails] = await Promise.all([ 9 | getUser(supabase), 10 | getUserDetails(supabase) 11 | ]); 12 | 13 | if (!user) { 14 | return redirect('/dashboard/signin'); 15 | } 16 | 17 | return
; 18 | } 19 | -------------------------------------------------------------------------------- /app/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import { getUser } from '@/utils/supabase/queries'; 2 | import { redirect } from 'next/navigation'; 3 | import { createClient } from '@/utils/supabase/server'; 4 | 5 | export default async function Dashboard() { 6 | const supabase = createClient(); 7 | const [user] = await Promise.all([getUser(supabase)]); 8 | 9 | if (!user) { 10 | return redirect('/dashboard/signin'); 11 | } else { 12 | redirect('/dashboard/main'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/dashboard/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import Settings from '@/components/dashboard/settings'; 2 | import { redirect } from 'next/navigation'; 3 | import { createClient } from '@/utils/supabase/server'; 4 | import { getUserDetails, getUser } from '@/utils/supabase/queries'; 5 | 6 | export default async function SettingsPage() { 7 | const supabase = createClient(); 8 | const [user, userDetails] = await Promise.all([ 9 | getUser(supabase), 10 | getUserDetails(supabase) 11 | ]); 12 | if (!user) { 13 | return redirect('/dashboard/signin'); 14 | } 15 | 16 | return ; 17 | } 18 | -------------------------------------------------------------------------------- /app/dashboard/signin/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import DefaultAuth from '@/components/auth'; 2 | import AuthUI from '@/components/auth/AuthUI'; 3 | import { redirect } from 'next/navigation'; 4 | import { createClient } from '@/utils/supabase/server'; 5 | import { cookies } from 'next/headers'; 6 | import { 7 | getAuthTypes, 8 | getViewTypes, 9 | getDefaultSignInView, 10 | getRedirectMethod 11 | } from '@/utils/auth-helpers/settings'; 12 | 13 | export default async function SignIn({ 14 | params, 15 | searchParams 16 | }: { 17 | params: { id: string }; 18 | searchParams: { disable_button: boolean }; 19 | }) { 20 | const { allowOauth, allowEmail, allowPassword } = getAuthTypes(); 21 | const viewTypes = getViewTypes(); 22 | const redirectMethod = getRedirectMethod(); 23 | 24 | // Declare 'viewProp' and initialize with the default value 25 | let viewProp: string; 26 | 27 | // Assign url id to 'viewProp' if it's a valid string and ViewTypes includes it 28 | if (typeof params.id === 'string' && viewTypes.includes(params.id)) { 29 | viewProp = params.id; 30 | } else { 31 | const preferredSignInView = 32 | cookies().get('preferredSignInView')?.value || null; 33 | viewProp = getDefaultSignInView(preferredSignInView); 34 | return redirect(`/dashboard/signin/${viewProp}`); 35 | } 36 | 37 | // Check if the user is already logged in and redirect to the account page if so 38 | const supabase = createClient(); 39 | 40 | const { 41 | data: { user } 42 | } = await supabase.auth.getUser(); 43 | 44 | if (user && viewProp !== 'update_password') { 45 | return redirect('/dashboard/main'); 46 | } else if (!user && viewProp === 'update_password') { 47 | return redirect('/dashboard/signin'); 48 | } 49 | 50 | return ( 51 | 52 |
53 | 62 |
63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /app/dashboard/signin/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from 'next/navigation'; 2 | import { getDefaultSignInView } from '@/utils/auth-helpers/settings'; 3 | import { cookies } from 'next/headers'; 4 | 5 | export default function SignIn() { 6 | const preferredSignInView = 7 | cookies().get('preferredSignInView')?.value || null; 8 | const defaultView = getDefaultSignInView(preferredSignInView); 9 | 10 | return redirect(`/dashboard/signin/${defaultView}`); 11 | } 12 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import SupabaseProvider from './supabase-provider'; 2 | import { PropsWithChildren } from 'react'; 3 | import '@/styles/globals.css'; 4 | import { ThemeProvider } from './theme-provider'; 5 | 6 | export const dynamic = 'force-dynamic'; 7 | 8 | export default function RootLayout({ 9 | // Layouts must accept a children prop. 10 | // This will be populated with nested layouts or pages 11 | children 12 | }: PropsWithChildren) { 13 | return ( 14 | 15 | 16 | 17 | Horizon UI Boilerplate - Launch your startup project 10X in a few 18 | moments - The best NextJS Boilerplate (This is an example) 19 | 20 | 21 | {/* */} 22 | 26 | 27 | {/* */} 28 | 29 | 33 | 37 | {/* */} 38 | 39 | 43 | 47 | 51 | {/* */} 52 | 56 | 57 | 58 | 62 | 66 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
{children}
77 |
78 |
79 | 80 | 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { getUser } from '@/utils/supabase/queries'; 2 | import { redirect } from 'next/navigation'; 3 | import { createClient } from '@/utils/supabase/server'; 4 | 5 | export default async function Dashboard() { 6 | const supabase = createClient(); 7 | const [user] = await Promise.all([getUser(supabase)]); 8 | 9 | if (!user) { 10 | return redirect('/dashboard/signin'); 11 | } else { 12 | redirect('/dashboard/main'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/supabase-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import type { Database } from '@/types/types_db'; 4 | import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs'; 5 | import type { SupabaseClient } from '@supabase/auth-helpers-nextjs'; 6 | import { useRouter } from 'next/navigation'; 7 | import { createContext, useContext, useEffect, useState } from 'react'; 8 | 9 | type SupabaseContext = { 10 | supabase: SupabaseClient; 11 | }; 12 | 13 | const Context = createContext(undefined); 14 | 15 | export default function SupabaseProvider({ 16 | children 17 | }: { 18 | children: React.ReactNode; 19 | }) { 20 | const [supabase] = useState(() => createPagesBrowserClient()); 21 | const router = useRouter(); 22 | 23 | useEffect(() => { 24 | const { 25 | data: { subscription } 26 | } = supabase.auth.onAuthStateChange((event) => { 27 | if (event === 'SIGNED_IN') router.refresh(); 28 | }); 29 | 30 | return () => { 31 | subscription.unsubscribe(); 32 | }; 33 | }, [router, supabase]); 34 | 35 | return {children}; 36 | } 37 | 38 | export const useSupabase = () => { 39 | const context = useContext(Context); 40 | 41 | if (context === undefined) { 42 | throw new Error('useSupabase must be used inside SupabaseProvider'); 43 | } 44 | 45 | return context; 46 | }; 47 | -------------------------------------------------------------------------------- /app/supabase-server.ts: -------------------------------------------------------------------------------- 1 | import { Database } from '@/types/types_db'; 2 | import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'; 3 | import { cookies } from 'next/headers'; 4 | import { cache } from 'react'; 5 | 6 | export const createServerSupabaseClient = cache(() => 7 | createServerComponentClient({ cookies }) 8 | ); 9 | -------------------------------------------------------------------------------- /app/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ThemeProvider as NextThemesProvider } from 'next-themes'; 4 | import type { ThemeProviderProps } from 'next-themes/dist/types'; 5 | import * as React from 'react'; 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children}; 9 | } 10 | -------------------------------------------------------------------------------- /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.ts", 8 | "css": "styles/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /components/MessageBox.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from './ui/card'; 2 | import ReactMarkdown from 'react-markdown'; 3 | 4 | export default function MessageBox(props: { output: string }) { 5 | const { output } = props; 6 | return ( 7 | 8 | 9 | {output ? output : 'Your generated response will appear here...'} 10 | 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /components/MessageBoxChat.tsx: -------------------------------------------------------------------------------- 1 | import { Card } from '@/components/ui/card'; 2 | import ReactMarkdown from 'react-markdown'; 3 | 4 | export default function MessageBox(props: { output: string }) { 5 | const { output } = props; 6 | return ( 7 | 12 | 13 | {output ? output : ''} 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /components/TextBlock.tsx: -------------------------------------------------------------------------------- 1 | interface Props { 2 | text: string; 3 | editable?: boolean; 4 | onChange?: (value: string) => void; 5 | } 6 | 7 | export const TextBlock: React.FC = ({ 8 | text, 9 | editable = false, 10 | onChange = () => {} 11 | }) => { 12 | return ( 13 |