├── .env.example ├── .gitignore ├── .vscode ├── extensions.json └── launch.json ├── README.md ├── astro.config.mjs ├── biome.json ├── package-lock.json ├── package.json ├── public ├── avatar.jpeg └── favicon.svg ├── src ├── components │ ├── AreaCharts.tsx │ ├── BarLists.tsx │ ├── Button.astro │ ├── DashboardFlyout.tsx │ ├── DashboardFlyoutMenu.tsx │ ├── DashboardHomePage.astro │ ├── DashboardMenu.tsx │ ├── DashboardStaticSidebar.astro │ ├── DashboardStats.astro │ ├── Hero.astro │ ├── Icons.tsx │ ├── LandingFlyout.tsx │ ├── LandingNavbar.astro │ ├── LoginForm.astro │ ├── Navbar.astro │ ├── PieChart.tsx │ └── RegisterForm.astro ├── config │ ├── dashboard.ts │ ├── navigation.ts │ └── site.ts ├── data │ ├── total-customers.json │ ├── total-sales-by-category.json │ └── total-sales.json ├── env.d.ts ├── layouts │ ├── DashboardLayout.astro │ └── Layout.astro ├── lib │ ├── supabase.ts │ └── utils.ts ├── pages │ ├── api │ │ └── auth │ │ │ ├── callback.ts │ │ │ ├── login.ts │ │ │ ├── logout.ts │ │ │ └── register.ts │ ├── dashboard.astro │ ├── dashboard │ │ ├── calendar.astro │ │ ├── documents.astro │ │ ├── projects.astro │ │ ├── reports.astro │ │ ├── settings.astro │ │ ├── team.astro │ │ └── team │ │ │ └── avengers.astro │ ├── index.astro │ ├── login.astro │ └── register.astro ├── styles │ └── globals.css └── types │ └── index.d.ts ├── tailwind.config.js └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | SUPABASE_URL= 2 | SUPABASE_ANON_KEY= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | 4 | # generated types 5 | .astro/ 6 | 7 | # dependencies 8 | node_modules/ 9 | 10 | # logs 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Astro Dashboard Starter 2 | 3 | Screenshot 2024-04-01 at 2 36 45 PM 4 | An open source dashboard project using Astro with API endpoints, Tailwind, and Supabase. 5 | 6 | > [!WARNING] 7 | > This is a work-in-progress in very early stages and will definitely undergo significant changes. Please feel free to track the progress on [X/Twitter](https://twitter.com/theAlexWhitmore). 8 | 9 | ## Purpose 10 | 11 | Astro has recently released 4.0 and is no longer just for content heavy websites (it hasn't been for a while). I want to build out a "web-like" application using as many of the features as possible to see if I can find the limits of Astro (I don't actually think there are any limits). This project will remain open source and eventually be submitted as a [theme](https://astro.build/themes/) to Astro. 12 | 13 | ## Getting started 14 | 15 | 1. Clone the repository: 16 | 17 | ```cli 18 | git clone https://github.com/alexwhitmore/astro-dashboard.git my-project 19 | ``` 20 | 21 | 2. Install dependencies: 22 | 23 | ```cli 24 | npm install 25 | ``` 26 | 27 | 3. Copy `.env.example` to `.env` and update the variables: 28 | 29 | ```cli 30 | cp .env.example .env 31 | ``` 32 | 33 | 4. Start the development server: 34 | 35 | ```cli 36 | npm run dev 37 | ``` 38 | 39 | ## Technology Used 40 | 41 | | Technology | Purpose | Link | 42 | | -------------------------------- | ---------------------------- | ---------------------------------------------------- | 43 | | Astro | Frontend and API endpoints | [Docs](https://docs.astro.build/en/getting-started/) | 44 | | TailwindCSS | Styling | [Docs](https://tailwindcss.com/) | 45 | | TailwindUI | Component Library | [Docs](https://tailwindui.com/) | 46 | | HeadlessUI | Accessible Component Library | [Docs](https://headlessui.com/) | 47 | | React | UI Library | [Docs](https://react.dev/) | 48 | | Tremor | Charts UI Library | [Docs](https://www.tremor.so/) | 49 | | Supabase | Database/storage/auth | [Docs](https://supabase.com/) | 50 | | Hero Icons (switching to Lucide) | Icons | [Docs](https://heroicons.com/) | 51 | | Lucide Icons | Icons | [Docs](https://lucide.dev/) | 52 | 53 | ## Planned Features 54 | 55 | - Task tracker, DnD (Kanban board) 56 | - Landing Page 57 | - Blog (with content collection) 58 | - Authentication (Email/password & OAuth) 59 | - Role-based access 60 | - GitHub actions for deployment 61 | - User dashboard 62 | 63 | If you have any other ideas of features I should add, please feel free to open up an issue or message me on [X/Twitter](https://twitter.com/theAlexWhitmore)! 64 | -------------------------------------------------------------------------------- /astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import tailwind from '@astrojs/tailwind'; 3 | import node from '@astrojs/node'; 4 | import react from "@astrojs/react"; 5 | 6 | // https://astro.build/config 7 | export default defineConfig({ 8 | output: 'server', 9 | adapter: node({ 10 | mode: 'standalone' 11 | }), 12 | integrations: [tailwind({ 13 | applyBaseStyles: false 14 | }), react()] 15 | }); -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.5.3/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "javascript": { 7 | "formatter": { 8 | "enabled": true, 9 | "indentStyle": "space", 10 | "indentWidth": 2, 11 | "lineWidth": 100, 12 | "quoteStyle": "single", 13 | "semicolons": "asNeeded", 14 | "trailingComma": "all" 15 | } 16 | }, 17 | "json": { 18 | "formatter": { 19 | "enabled": true, 20 | "indentStyle": "space", 21 | "indentWidth": 2 22 | } 23 | }, 24 | "linter": { 25 | "enabled": true, 26 | "rules": { 27 | "recommended": true, 28 | "suspicious": { 29 | "noArrayIndexKey": "off" 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-dashboard", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro check && astro build", 9 | "preview": "astro preview", 10 | "astro": "astro" 11 | }, 12 | "dependencies": { 13 | "@astrojs/check": "^0.3.1", 14 | "@astrojs/node": "^7.0.0", 15 | "@astrojs/react": "^3.0.7", 16 | "@astrojs/tailwind": "^5.0.3", 17 | "@headlessui/react": "^1.7.17", 18 | "@heroicons/react": "^2.0.18", 19 | "@supabase/supabase-js": "^2.39.0", 20 | "@tremor/react": "^3.11.1", 21 | "@types/react": "^18.2.42", 22 | "@types/react-dom": "^18.2.17", 23 | "astro": "^4.0.3", 24 | "class-variance-authority": "^0.7.0", 25 | "clsx": "^2.0.0", 26 | "react": "^18.2.0", 27 | "react-dom": "^18.2.0", 28 | "tailwind-merge": "^2.1.0", 29 | "tailwindcss": "^3.3.6", 30 | "tailwindcss-animate": "^1.0.7", 31 | "typescript": "^5.3.3" 32 | }, 33 | "devDependencies": { 34 | "@tailwindcss/forms": "^0.5.7", 35 | "@tailwindcss/typography": "^0.5.10" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/avatar.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexwhitmore/astro-dashboard/c4f1cd52b67c61b545c7945ba74bc5181cd7d490/public/avatar.jpeg -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/AreaCharts.tsx: -------------------------------------------------------------------------------- 1 | import { AreaChart, Card, Title } from '@tremor/react' 2 | 3 | const chartdata = [ 4 | { 5 | date: 'Jan 23', 6 | 2023: 2890, 7 | '2022': 2338, 8 | }, 9 | { 10 | date: 'Feb 23', 11 | 2023: 2756, 12 | '2022': 2103, 13 | }, 14 | { 15 | date: 'Mar 23', 16 | 2023: 3323, 17 | '2022': 2194, 18 | }, 19 | { 20 | date: 'Apr 23', 21 | 2023: 3470, 22 | '2022': 2108, 23 | }, 24 | { 25 | date: 'May 23', 26 | 2023: 3475, 27 | '2022': 1812, 28 | }, 29 | { 30 | date: 'Jun 23', 31 | 2023: 3129, 32 | '2022': 1726, 33 | }, 34 | ] 35 | 36 | const valueFormatter = function (number) { 37 | return '$ ' + new Intl.NumberFormat('us').format(number).toString() 38 | } 39 | 40 | export function AreaCharts() { 41 | return ( 42 | 43 | 6 month revenue comparison 44 | 53 | 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /src/components/BarLists.tsx: -------------------------------------------------------------------------------- 1 | import { BarList, Bold, Card, Flex, Text, Title } from '@tremor/react' 2 | 3 | const data = [ 4 | { 5 | name: 'Twitter', 6 | value: 456, 7 | href: 'https://twitter.com', 8 | icon: function TwitterIcon() { 9 | return ( 10 | 16 | 17 | 18 | 19 | ) 20 | }, 21 | }, 22 | { 23 | name: 'Google', 24 | value: 351, 25 | href: 'https://google.com', 26 | icon: function GoogleIcon() { 27 | return ( 28 | 34 | 35 | 36 | 37 | ) 38 | }, 39 | }, 40 | { 41 | name: 'GitHub', 42 | value: 271, 43 | href: 'https://github.com/alexwhitmore', 44 | icon: function GitHubIcon() { 45 | return ( 46 | 52 | 53 | 54 | 55 | ) 56 | }, 57 | }, 58 | { 59 | name: 'Reddit', 60 | value: 191, 61 | href: 'https://reddit.com', 62 | icon: function RedditIcon() { 63 | return ( 64 | 70 | 71 | 72 | 73 | ) 74 | }, 75 | }, 76 | { 77 | name: 'Youtube', 78 | value: 91, 79 | href: 'https://www.youtube.com', 80 | icon: function YouTubeIcon() { 81 | return ( 82 | 88 | 89 | 90 | 91 | ) 92 | }, 93 | }, 94 | ] 95 | 96 | export function BarLists() { 97 | return ( 98 | 99 | Traffic sources 100 | 101 | 102 | Source 103 | 104 | 105 | Visits 106 | 107 | 108 | 109 | 110 | ) 111 | } 112 | -------------------------------------------------------------------------------- /src/components/Button.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { variant = 'default' } = Astro.props 3 | import { cva } from 'class-variance-authority' 4 | import { cn } from '@/lib/utils' 5 | 6 | export const buttonVariants = cva( 7 | 'rounded-md px-3 py-2 text-sm text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2', 8 | { 9 | variants: { 10 | variant: { 11 | default: 'bg-primary hover:bg-primary/80 focus-visible:outline-primary', 12 | secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 focus-visible:outline-secondary', 13 | ghost: 'hover:bg-accent hover:text-accent-foreground', 14 | outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', 15 | }, 16 | }, 17 | } 18 | ) 19 | --- 20 | 21 | 24 | -------------------------------------------------------------------------------- /src/components/DashboardFlyout.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment } from 'react' 2 | import { Dialog, Transition } from '@headlessui/react' 3 | import { cn } from '@/lib/utils' 4 | import { Icons } from './Icons' 5 | import { navigation, teams } from '@/config/dashboard' 6 | import type { DashboardFlyoutProps } from '@/types' 7 | 8 | export function DashboardFlyout({ sidebarOpen = false, setSidebarOpen, path }: DashboardFlyoutProps) { 9 | return ( 10 | 11 | 12 | 20 |
21 | 22 | 23 |
24 | 32 | 33 | 41 |
42 | 46 |
47 |
48 | 49 |
50 |
51 | 62 | 63 | 64 |
65 | 136 |
137 |
138 |
139 |
140 |
141 |
142 | ) 143 | } 144 | -------------------------------------------------------------------------------- /src/components/DashboardFlyoutMenu.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { Icons } from './Icons' 3 | import { DashboardMenu } from './DashboardMenu' 4 | import { DashboardFlyout } from './DashboardFlyout' 5 | 6 | export function DashboardFlyoutMenu({ path }: { path: string }) { 7 | const [sidebarOpen, setSidebarOpen] = useState(false) 8 | 9 | return ( 10 | <> 11 | 12 |
13 |
14 |
15 | 19 | 20 | {/* Separator */} 21 | 54 |
55 | 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /src/components/DashboardHomePage.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import DashboardStaticSidebar from '@/components/DashboardStaticSidebar.astro' 3 | import { DashboardFlyoutMenu } from './DashboardFlyoutMenu' 4 | import { navigation, teams } from '@/config/dashboard' 5 | const path = Astro.url.pathname 6 | const { title } = Astro.props 7 | --- 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {title} 18 | 19 | 20 | 21 | 22 |
23 |
24 | 25 |
26 |
27 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/DashboardMenu.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment } from 'react' 2 | import { Menu, Transition } from '@headlessui/react' 3 | import { Icons } from './Icons' 4 | import { cn } from '@/lib/utils' 5 | 6 | const userNavigation = [ 7 | { name: 'Your profile', href: '#' }, 8 | { name: 'Sign out', href: '/api/auth/logout' }, 9 | ] 10 | 11 | export function DashboardMenu() { 12 | return ( 13 | 14 | 15 | Open user menu 16 | 17 | 18 | 21 | 23 | 24 | 32 | 33 | {userNavigation.map((item) => ( 34 | 35 | {({ active }) => ( 36 | 37 | {item.name} 38 | 39 | )} 40 | 41 | ))} 42 | 43 | 44 | 45 | ) 46 | } 47 | -------------------------------------------------------------------------------- /src/components/DashboardStaticSidebar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | const { navigation, teams } = Astro.props; 3 | const path = Astro.url.pathname; 4 | import { cn } from "@/lib/utils"; 5 | import { Icons } from "./Icons"; 6 | import type { NavigationItem, Team } from "@/types"; 7 | --- 8 | 9 | 107 | -------------------------------------------------------------------------------- /src/components/DashboardStats.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { PieChart } from './PieChart' 3 | import { BarLists } from './BarLists' 4 | import { AreaCharts } from './AreaCharts' 5 | --- 6 | 7 |
8 |
9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 | -------------------------------------------------------------------------------- /src/components/Hero.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '@/layouts/Layout.astro' 3 | import LandingNavbar from './LandingNavbar.astro' 4 | import Button from './Button.astro' 5 | --- 6 | 7 | 8 | 9 |
10 |
11 | 29 | 40 |
41 |
42 |
43 |
44 |

We’re changing the way people connect.

45 |

46 | Cupidatat minim id magna ipsum sint dolor qui. Sunt sit in quis cupidatat mollit aute velit. Et labore 47 | commodo nulla aliqua proident mollit ullamco exercitation tempor. Sint aliqua anim nulla sunt mollit id 48 | pariatur in voluptate cillum. 49 |

50 |
51 | 54 | 55 | Live demo 56 | 57 |
58 |
59 |
60 |
63 |
64 | 69 |
70 |
71 |
72 |
73 |
74 | 79 |
80 |
81 |
82 | 87 |
88 |
89 |
90 |
91 |
92 | 97 |
98 |
99 |
100 | 105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | -------------------------------------------------------------------------------- /src/components/Icons.tsx: -------------------------------------------------------------------------------- 1 | export const Icons = { 2 | Menu: ({ ...props }) => ( 3 | 11 | 16 | 17 | ), 18 | Bell: ({ ...props }) => ( 19 | 28 | Bell Icon 29 | 35 | 36 | ), 37 | Search: ({ ...props }) => ( 38 | 46 | Search Icon 47 | 52 | 53 | ), 54 | Calendar: ({ ...props }) => ( 55 | 64 | Calendar Icon 65 | 71 | 72 | ), 73 | PieChart: ({ ...props }) => ( 74 | 82 | Pie Chart Icon 83 | 88 | 93 | 94 | ), 95 | Settings: ({ ...props }) => ( 96 | 117 | ), 118 | Document: ({ ...props }) => ( 119 | 128 | Document Icon 129 | 135 | 136 | ), 137 | Folder: ({ ...props }) => ( 138 | 147 | Folder Icon 148 | 154 | 155 | ), 156 | Home: ({ ...props }) => ( 157 | 166 | Home Icon 167 | 173 | 174 | ), 175 | Users: ({ ...props }) => ( 176 | 185 | Users Icon 186 | 192 | 193 | ), 194 | X: ({ ...props }) => ( 195 | 203 | 208 | 209 | ), 210 | ChevronDown: ({ ...props }) => ( 211 | 219 | 224 | 225 | ), 226 | Logo: ({ ...props }) => ( 227 | 242 | ), 243 | GitHub: ({ ...props }) => ( 244 | 251 | ), 252 | Google: ({ ...props }) => ( 253 | 263 | ), 264 | }; 265 | -------------------------------------------------------------------------------- /src/components/LandingFlyout.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { Dialog } from '@headlessui/react' 3 | import { Icons } from './Icons' 4 | import { navigation } from '@/config/navigation' 5 | 6 | export function LandingFlyout() { 7 | const [mobileMenuOpen, setMobileMenuOpen] = useState(false) 8 | 9 | return ( 10 | <> 11 |
12 | 19 |
20 | 21 | 22 |
23 | 24 |
25 | 26 | Your Company 27 | 29 | 33 |
34 |
35 |
36 |
37 | {navigation.map((item) => ( 38 | 42 | {item.name} 43 | 44 | ))} 45 |
46 | 51 |
52 |
53 |
54 |
55 | 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /src/components/LandingNavbar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { navigation } from '@/config/navigation' 3 | import { Icons } from './Icons' 4 | import { LandingFlyout } from './LandingFlyout' 5 | import Button from './Button.astro' 6 | --- 7 | 8 |
9 | 34 |
35 | -------------------------------------------------------------------------------- /src/components/LoginForm.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { Icons } from './Icons' 3 | import { buttonVariants } from './Button.astro' 4 | import { cn } from '@/lib/utils' 5 | --- 6 | 7 |
8 |
9 | 10 |

Sign in to your account

11 |
12 | 13 |
14 |
15 |
16 |
17 | 18 |
19 | 27 |
28 |
29 | 30 |
31 | 32 |
33 | 41 |
42 |
43 | 44 |
45 |
46 | 52 | 53 |
54 | 55 |
56 | Forgot password? 57 |
58 |
59 | 60 |
61 | 62 |
63 |
64 | 65 |
66 |
67 | 70 |
71 | Or continue with 72 |
73 |
74 | 75 |
76 | 85 | 86 |
87 | 96 |
97 |
98 |
99 | 100 |

101 | Not a member yet? 102 | Sign up here! 103 |

104 |
105 |
106 |
107 | -------------------------------------------------------------------------------- /src/components/Navbar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { navigation } from '@/config/navigation' 3 | import { buttonVariants } from './Button.astro' 4 | import { cn } from '@/lib/utils' 5 | import { Icons } from './Icons' 6 | --- 7 | 8 |
9 | 44 | 45 | 90 |
91 | -------------------------------------------------------------------------------- /src/components/PieChart.tsx: -------------------------------------------------------------------------------- 1 | import { Card, DonutChart, Title } from '@tremor/react' 2 | import totalSalesByCountry from '../data/total-sales.json' 3 | import totalCustomers from '../data/total-customers.json' 4 | import totalSalesByCategory from '../data/total-sales-by-category.json' 5 | 6 | const valueFormatter = (number: number) => 7 | `$ ${new Intl.NumberFormat('us').format(number).toString()}` 8 | 9 | const numberFormatter = (number: number) => new Intl.NumberFormat('us').format(number).toString() 10 | 11 | export function PieChart() { 12 | return ( 13 | <> 14 | 15 | Total sales 16 | 24 | 25 | 26 | Total customers by country 27 | 35 | 36 | 37 | Sales categories 38 | 46 | 47 | 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /src/components/RegisterForm.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import { Icons } from './Icons' 3 | import { buttonVariants } from './Button.astro' 4 | import { cn } from '@/lib/utils' 5 | --- 6 | 7 |
8 |
9 | 10 |

Sign up for an account

11 |
12 | 13 |
14 |
15 |
16 |
17 | 18 |
19 | 27 |
28 |
29 | 30 |
31 | 32 |
33 | 41 |
42 |
43 | 44 |
45 |
46 | 52 | 53 |
54 | 55 |
56 | Forgot password? 57 |
58 |
59 | 60 |
61 | 62 |
63 |
64 | 65 |
66 |
67 | 70 |
71 | Or continue with 72 |
73 |
74 | 75 |
76 | 85 | 86 |
87 | 96 |
97 |
98 |
99 | 100 |

101 | Already a member? 102 | Sign in here! 103 |

104 |
105 |
106 |
107 | -------------------------------------------------------------------------------- /src/config/dashboard.ts: -------------------------------------------------------------------------------- 1 | import { Icons } from '@/components/Icons' 2 | 3 | export const navigation = [ 4 | { name: 'Dashboard', href: '/dashboard', icon: Icons.Home }, 5 | { name: 'Team', href: '/dashboard/team', icon: Icons.Users }, 6 | { name: 'Projects', href: '/dashboard/projects', icon: Icons.Folder }, 7 | { name: 'Calendar', href: '/dashboard/calendar', icon: Icons.Calendar }, 8 | { name: 'Documents', href: '/dashboard/documents', icon: Icons.Document }, 9 | ] 10 | 11 | export const teams = [ 12 | { id: 1, name: 'Avengers', href: '/dashboard/team/avengers', initial: 'A', current: false }, 13 | // { id: 2, name: 'Team 2', href: '#', initial: 'T', current: false }, 14 | // { id: 3, name: 'Developer Hangout', href: '#', initial: 'W', current: false }, 15 | ] 16 | -------------------------------------------------------------------------------- /src/config/navigation.ts: -------------------------------------------------------------------------------- 1 | export const navigation = [ 2 | { 3 | name: 'Dashboard', 4 | href: '/', 5 | }, 6 | { 7 | name: 'Team', 8 | href: '#team', 9 | }, 10 | { 11 | name: 'Projects', 12 | href: '#projects', 13 | }, 14 | { 15 | name: 'Calendar', 16 | href: '#calendar', 17 | }, 18 | ] 19 | -------------------------------------------------------------------------------- /src/config/site.ts: -------------------------------------------------------------------------------- 1 | export const siteConfig = [ 2 | { 3 | companyName: 'Your company name', 4 | }, 5 | ] 6 | -------------------------------------------------------------------------------- /src/data/total-customers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "name": "Canada", "number": 56 }, 3 | { "name": "United Kingdom", "number": 24 }, 4 | { "name": "Australia", "number": 19 }, 5 | { "name": "United States", "number": 187 } 6 | ] -------------------------------------------------------------------------------- /src/data/total-sales-by-category.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "name": "Electronics", "sales": 15000 }, 3 | { "name": "Clothing", "sales": 12000 }, 4 | { "name": "Home & Garden", "sales": 8000 }, 5 | { "name": "Beauty & Health", "sales": 5000 }, 6 | { "name": "Sports & Outdoors", "sales": 4000 }, 7 | { "name": "Books", "sales": 3000 }, 8 | { "name": "Other", "sales": 2000 } 9 | ] 10 | -------------------------------------------------------------------------------- /src/data/total-sales.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "name": "United States", "sales": 20000 }, 3 | { "name": "United Kingdom", "sales": 10000 }, 4 | { "name": "Canada", "sales": 7000 }, 5 | { "name": "Australia", "sales": 5000 }, 6 | { "name": "Germany", "sales": 4000 }, 7 | { "name": "France", "sales": 3000 }, 8 | { "name": "Japan", "sales": 2000 } 9 | ] 10 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | readonly SUPABASE_URL: string 5 | readonly SUPABASE_ANON_KEY: string 6 | } 7 | 8 | interface ImportMeta { 9 | readonly env: ImportMetaEnv 10 | } 11 | -------------------------------------------------------------------------------- /src/layouts/DashboardLayout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import "@/styles/globals.css"; 3 | import DashboardStaticSidebar from "@/components/DashboardStaticSidebar.astro"; 4 | import { DashboardFlyoutMenu } from "@/components/DashboardFlyoutMenu"; 5 | import { navigation, teams } from "@/config/dashboard"; 6 | const path = Astro.url.pathname; 7 | 8 | const { title } = Astro.props; 9 | --- 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {title} 20 | 21 | 22 | 23 | 24 |
25 | 26 |
27 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/layouts/Layout.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import '../styles/globals.css' 3 | interface Props { 4 | title: string 5 | } 6 | 7 | const { title } = Astro.props 8 | --- 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {title} 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/lib/supabase.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from '@supabase/supabase-js' 2 | 3 | export const supabase = createClient(import.meta.env.SUPABASE_URL, import.meta.env.SUPABASE_ANON_KEY, { 4 | auth: { 5 | flowType: 'pkce', 6 | }, 7 | }) 8 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /src/pages/api/auth/callback.ts: -------------------------------------------------------------------------------- 1 | import type { APIRoute } from 'astro' 2 | import { supabase } from '@/lib/supabase' 3 | 4 | export const GET: APIRoute = async ({ url, cookies, redirect }) => { 5 | const authCode = url.searchParams.get('code') 6 | 7 | if (!authCode) { 8 | return new Response('No code provided', { status: 400 }) 9 | } 10 | 11 | const { data, error } = await supabase.auth.exchangeCodeForSession(authCode) 12 | 13 | if (error) { 14 | return new Response(error.message, { status: 500 }) 15 | } 16 | 17 | const { access_token, refresh_token } = data.session 18 | 19 | cookies.set('sb-access-token', access_token, { 20 | path: '/', 21 | }) 22 | cookies.set('sb-refresh-token', refresh_token, { 23 | path: '/', 24 | }) 25 | 26 | return redirect('/dashboard') 27 | } 28 | -------------------------------------------------------------------------------- /src/pages/api/auth/login.ts: -------------------------------------------------------------------------------- 1 | // import type { APIRoute } from 'astro' 2 | // import { supabase } from '@/lib/supabase' 3 | 4 | // export const POST: APIRoute = async ({ request, cookies, redirect }) => { 5 | // const formData = await request.formData() 6 | // const email = formData.get('email')?.toString() 7 | // const password = formData.get('password')?.toString() 8 | 9 | // if (!email || !password) { 10 | // return new Response('Email and password are required', { status: 400 }) 11 | // } 12 | 13 | // const { data, error } = await supabase.auth.signInWithPassword({ 14 | // email, 15 | // password, 16 | // }) 17 | 18 | // if (error) { 19 | // return new Response(error.message, { status: 500 }) 20 | // } 21 | 22 | // const { access_token, refresh_token } = data.session 23 | // cookies.set('sb-access-token', access_token, { 24 | // path: '/', 25 | // }) 26 | // cookies.set('sb-refresh-token', refresh_token, { 27 | // path: '/', 28 | // }) 29 | // return redirect('/dashboard') 30 | // } 31 | 32 | import type { APIRoute } from 'astro' 33 | import { supabase } from '../../../lib/supabase' 34 | 35 | export const POST: APIRoute = async ({ request, cookies, redirect }) => { 36 | const formData = await request.formData() 37 | const email = formData.get('email')?.toString() 38 | const password = formData.get('password')?.toString() 39 | const provider = formData.get('provider')?.toString() 40 | 41 | const validProviders = ['google', 'github', 'discord'] 42 | 43 | if (provider && validProviders.includes(provider)) { 44 | const { data, error } = await supabase.auth.signInWithOAuth({ 45 | provider: provider, 46 | options: { 47 | redirectTo: 'http://localhost:4321/api/auth/callback', 48 | }, 49 | }) 50 | 51 | if (error) { 52 | return new Response(error.message, { status: 500 }) 53 | } 54 | 55 | return redirect(data.url) 56 | } 57 | 58 | if (!email || !password) { 59 | return new Response('Email and password are required', { status: 400 }) 60 | } 61 | 62 | const { data, error } = await supabase.auth.signInWithPassword({ 63 | email, 64 | password, 65 | }) 66 | 67 | if (error) { 68 | return new Response(error.message, { status: 500 }) 69 | } 70 | 71 | const { access_token, refresh_token } = data.session 72 | cookies.set('sb-access-token', access_token, { 73 | path: '/', 74 | }) 75 | cookies.set('sb-refresh-token', refresh_token, { 76 | path: '/', 77 | }) 78 | return redirect('/dashboard') 79 | } 80 | -------------------------------------------------------------------------------- /src/pages/api/auth/logout.ts: -------------------------------------------------------------------------------- 1 | import type { APIRoute } from 'astro' 2 | 3 | export const GET: APIRoute = async ({ cookies, redirect }) => { 4 | cookies.delete('sb-access-token', { path: '/' }) 5 | cookies.delete('sb-refresh-token', { path: '/' }) 6 | return redirect('/login') 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/api/auth/register.ts: -------------------------------------------------------------------------------- 1 | import type { APIRoute } from 'astro' 2 | import { supabase } from '@/lib/supabase' 3 | 4 | export const POST: APIRoute = async ({ request, redirect }) => { 5 | const formData = await request.formData() 6 | const email = formData.get('email')?.toString() 7 | const password = formData.get('password')?.toString() 8 | 9 | if (!email || !password) { 10 | return new Response('Email and password is required', { status: 400 }) 11 | } 12 | 13 | const { error } = await supabase.auth.signUp({ email, password }) 14 | 15 | if (error) { 16 | return new Response(`${error.message}`, { status: 500 }) 17 | } 18 | 19 | return redirect('/dashboard') 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/dashboard.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import DashboardLayout from '@/layouts/DashboardLayout.astro' 3 | import DashboardStats from '@/components/DashboardStats.astro' 4 | 5 | const { cookies, redirect } = Astro 6 | const accessToken = cookies.get('sb-access-token') 7 | const refreshToken = cookies.get('sb-refresh-token') 8 | 9 | if (!accessToken && !refreshToken) { 10 | return redirect('/login') 11 | } 12 | --- 13 | 14 | 15 |
16 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /src/pages/dashboard/calendar.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import DashboardLayout from '@/layouts/DashboardLayout.astro' 3 | 4 | const { cookies, redirect } = Astro 5 | const accessToken = cookies.get('sb-access-token') 6 | const refreshToken = cookies.get('sb-refresh-token') 7 | 8 | if (!accessToken && !refreshToken) { 9 | return redirect('/login') 10 | } 11 | --- 12 | 13 | 14 |

Hello from the Dashboard calendar page!

15 |
16 | -------------------------------------------------------------------------------- /src/pages/dashboard/documents.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import DashboardLayout from '@/layouts/DashboardLayout.astro' 3 | 4 | const { cookies, redirect } = Astro 5 | const accessToken = cookies.get('sb-access-token') 6 | const refreshToken = cookies.get('sb-refresh-token') 7 | 8 | if (!accessToken && !refreshToken) { 9 | return redirect('/login') 10 | } 11 | --- 12 | 13 | 14 |
15 |
16 |

Applicant Information

17 |

Personal details and application.

18 |
19 |
20 |
21 |
22 |
Full name
23 |
Margot Foster
24 |
25 |
26 |
Application for
27 |
Backend Developer
28 |
29 |
30 |
Email address
31 |
margotfoster@example.com
32 |
33 |
34 |
Salary expectation
35 |
$120,000
36 |
37 |
38 |
About
39 |
Fugiat ipsum ipsum deserunt culpa aute sint do nostrud anim incididunt cillum culpa consequat. Excepteur 41 | qui ipsum aliquip consequat sint. Sit id mollit nulla mollit nostrud in ea officia proident. Irure nostrud 42 | pariatur mollit ad adipisicing reprehenderit deserunt qui eu.
44 |
45 |
46 |
Attachments
47 |
48 |
    49 |
  • 50 |
    51 | 62 |
    63 | resume_back_end_developer.pdf 64 | 2.4mb 65 |
    66 |
    67 |
    68 | Download 69 |
    70 |
  • 71 |
  • 72 |
    73 | 84 |
    85 | coverletter_back_end_developer.pdf 86 | 4.5mb 87 |
    88 |
    89 |
    90 | Download 91 |
    92 |
  • 93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | -------------------------------------------------------------------------------- /src/pages/dashboard/projects.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import DashboardLayout from '@/layouts/DashboardLayout.astro' 3 | 4 | const { cookies, redirect } = Astro 5 | const accessToken = cookies.get('sb-access-token') 6 | const refreshToken = cookies.get('sb-refresh-token') 7 | 8 | if (!accessToken && !refreshToken) { 9 | return redirect('/login') 10 | } 11 | --- 12 | 13 | 14 |

Hello from the Dashboard projects page!

15 |
16 | -------------------------------------------------------------------------------- /src/pages/dashboard/reports.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import DashboardLayout from '@/layouts/DashboardLayout.astro' 3 | 4 | const { cookies, redirect } = Astro 5 | const accessToken = cookies.get('sb-access-token') 6 | const refreshToken = cookies.get('sb-refresh-token') 7 | 8 | if (!accessToken && !refreshToken) { 9 | return redirect('/login') 10 | } 11 | --- 12 | 13 | 14 |

Hello from the Dashboard reports page!

15 |
16 | -------------------------------------------------------------------------------- /src/pages/dashboard/settings.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import DashboardLayout from '@/layouts/DashboardLayout.astro' 3 | import Button from '@/components/Button.astro' 4 | --- 5 | 6 | 7 |
8 |
9 |
10 |
11 |
12 |

Personal Information

13 |

Use a permanent address where you can receive mail.

16 |
17 | 18 |
19 |
20 |
21 | 26 |
27 | 32 |

JPG, GIF or PNG. 1MB max.

33 |
34 |
35 | 36 |
37 | 38 |
39 | 46 |
47 |
48 | 49 |
50 | 51 |
52 | 59 |
60 |
61 | 62 |
63 | 64 |
65 | 72 |
73 |
74 | 75 |
76 | 77 |
78 |
81 | @ 82 | 90 |
91 |
92 |
93 | 94 |
95 | 96 |
97 | 106 |
107 |
108 |
109 | 110 |
111 | 112 |
113 |
114 |
115 | 116 |
117 |
118 |

Change password

119 |

Update your password associated with your account.

122 |
123 | 124 |
125 |
126 |
127 | 128 |
129 | 136 |
137 |
138 | 139 |
140 | 141 |
142 | 149 |
150 |
151 | 152 |
153 | 154 |
155 | 162 |
163 |
164 |
165 | 166 |
167 | 168 |
169 |
170 |
171 | 172 |
173 |
174 |

Delete account

175 |

No longer want to use our service? You can delete your account here. This action is not reversible. All 177 | information related to this account will be deleted permanently.

179 |
180 | 181 |
182 | 187 |
188 |
189 |
190 |
191 |
192 |
193 | -------------------------------------------------------------------------------- /src/pages/dashboard/team.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import DashboardLayout from '@/layouts/DashboardLayout.astro' 3 | 4 | const { cookies, redirect } = Astro 5 | const accessToken = cookies.get('sb-access-token') 6 | const refreshToken = cookies.get('sb-refresh-token') 7 | 8 | if (!accessToken && !refreshToken) { 9 | return redirect('/login') 10 | } 11 | --- 12 | 13 | 14 |

Hello from the Dashboard team page!

15 |
16 | -------------------------------------------------------------------------------- /src/pages/dashboard/team/avengers.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import DashboardLayout from '../../../layouts/DashboardLayout.astro' 3 | --- 4 | 5 | 6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |

18 | Avengers 19 |

20 |
21 |

Overview of the Avengers team

22 |
23 |
Development
27 |
28 | 29 | 30 |
31 |
32 |

Number of team members

33 |

34 | 12 35 |

36 |
37 |
38 |

Number of active features

39 |

40 | 6 41 |

42 |
43 |
44 |

Average Task Completion Time

45 |

46 | 3 days 47 |

48 |
49 |
50 |

Project Completion Rate

51 |

52 | 98.5% 53 |

54 |
55 |
56 |
57 | 58 | 59 |
60 |

Team members

61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 77 | 78 | 79 | 80 | 81 | 91 | 96 | 97 | 100 | 101 | 102 |
MemberEmail
82 |
83 | 88 |
Michael Foster
89 |
90 |
92 |
93 |
michael.foster@dashboard.com
94 |
95 |
103 |
104 |
105 |
106 |
107 | -------------------------------------------------------------------------------- /src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '@/layouts/Layout.astro' 3 | // import Navbar from '@/components/Navbar.astro' 4 | import Hero from '@/components/Hero.astro' 5 | // import { Hero } from '@/components/Hero' 6 | --- 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/pages/login.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '@/layouts/Layout.astro' 3 | import LoginForm from '@/components/LoginForm.astro' 4 | 5 | const { cookies, redirect } = Astro 6 | 7 | const accessToken = cookies.get('sb-access-token') 8 | const refreshToken = cookies.get('sb-refresh-token') 9 | 10 | if (accessToken && refreshToken) { 11 | return redirect('/dashboard') 12 | } 13 | --- 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/pages/register.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import Layout from '@/layouts/Layout.astro' 3 | import RegisterForm from '@/components/RegisterForm.astro' 4 | 5 | const { cookies, redirect } = Astro 6 | 7 | const accessToken = cookies.get('sb-access-token') 8 | const refreshToken = cookies.get('sb-refresh-token') 9 | 10 | if (accessToken && refreshToken) { 11 | return redirect('/dashboard') 12 | } 13 | --- 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/styles/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.5rem; 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 | font-feature-settings: 'rlig' 1, 'calt' 1; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | export type DashboardFlyoutProps = { 2 | sidebarOpen: boolean 3 | setSidebarOpen: (open: boolean) => void 4 | path: string 5 | } 6 | 7 | export type NavigationItem = { 8 | name: string 9 | href: string 10 | current: boolean 11 | icon: any 12 | } 13 | 14 | export type Team = { 15 | id: number 16 | name: string 17 | href: string 18 | initial: string 19 | current: boolean 20 | } 21 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | 3 | module.exports = { 4 | content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}', './node_modules/@tremor/**/*.{js,ts,jsx,tsx}'], 5 | darkMode: ['class'], 6 | theme: { 7 | transparent: 'transparent', 8 | current: 'currentColor', 9 | container: { 10 | center: true, 11 | padding: '2rem', 12 | screens: { 13 | '2xl': '1400px', 14 | }, 15 | }, 16 | extend: { 17 | colors: { 18 | border: 'hsl(var(--border))', 19 | input: 'hsl(var(--input))', 20 | ring: 'hsl(var(--ring))', 21 | background: 'hsl(var(--background))', 22 | foreground: 'hsl(var(--foreground))', 23 | primary: { 24 | DEFAULT: 'hsl(var(--primary))', 25 | foreground: 'hsl(var(--primary-foreground))', 26 | }, 27 | secondary: { 28 | DEFAULT: 'hsl(var(--secondary))', 29 | foreground: 'hsl(var(--secondary-foreground))', 30 | }, 31 | destructive: { 32 | DEFAULT: 'hsl(var(--destructive))', 33 | foreground: 'hsl(var(--destructive-foreground))', 34 | }, 35 | muted: { 36 | DEFAULT: 'hsl(var(--muted))', 37 | foreground: 'hsl(var(--muted-foreground))', 38 | }, 39 | accent: { 40 | DEFAULT: 'hsl(var(--accent))', 41 | foreground: 'hsl(var(--accent-foreground))', 42 | }, 43 | popover: { 44 | DEFAULT: 'hsl(var(--popover))', 45 | foreground: 'hsl(var(--popover-foreground))', 46 | }, 47 | card: { 48 | DEFAULT: 'hsl(var(--card))', 49 | foreground: 'hsl(var(--card-foreground))', 50 | }, 51 | tremor: { 52 | brand: { 53 | faint: '#eff6ff', // blue-50 54 | muted: '#bfdbfe', // blue-200 55 | subtle: '#60a5fa', // blue-400 56 | DEFAULT: '#3b82f6', // blue-500 57 | emphasis: '#1d4ed8', // blue-700 58 | inverted: '#ffffff', // white 59 | }, 60 | background: { 61 | muted: '#f9fafb', // gray-50 62 | subtle: '#f3f4f6', // gray-100 63 | DEFAULT: 'hsl(var(--card))', // white 64 | emphasis: '#374151', // gray-700 65 | }, 66 | border: { 67 | DEFAULT: '#e5e7eb', // gray-200 68 | }, 69 | ring: { 70 | DEFAULT: '#e5e7eb', // gray-200 71 | }, 72 | content: { 73 | subtle: '#9ca3af', // gray-400 74 | DEFAULT: '#6b7280', // gray-500 75 | emphasis: '#374151', // gray-700 76 | strong: '#111827', // gray-900 77 | inverted: '#ffffff', // white 78 | }, 79 | }, 80 | 'dark-tremor': { 81 | brand: { 82 | faint: '#0B1229', // custom 83 | muted: '#172554', // blue-950 84 | subtle: '#1e40af', // blue-800 85 | DEFAULT: '#3b82f6', // blue-500 86 | emphasis: '#60a5fa', // blue-400 87 | inverted: '#030712', // gray-950 88 | }, 89 | background: { 90 | muted: '#131A2B', // custom 91 | subtle: '#1f2937', // gray-800 92 | DEFAULT: 'hsl(var(--card))', // gray-900 93 | emphasis: '#d1d5db', // gray-300 94 | }, 95 | border: { 96 | DEFAULT: 'hsl(var(--border))', // gray-800 97 | }, 98 | ring: { 99 | DEFAULT: 'hsl(var(--border))', // gray-800 100 | }, 101 | content: { 102 | subtle: '#4b5563', // gray-600 103 | DEFAULT: '#6b7280', // gray-500 104 | emphasis: '#e5e7eb', // gray-200 105 | strong: '#f9fafb', // gray-50 106 | inverted: '#000000', // black 107 | }, 108 | }, 109 | }, 110 | borderRadius: { 111 | lg: 'var(--radius)', 112 | md: 'calc(var(--radius) - 2px)', 113 | sm: 'calc(var(--radius) - 4px)', 114 | }, 115 | keyframes: { 116 | 'accordion-down': { 117 | from: { height: 0 }, 118 | to: { height: 'var(--radix-accordion-content-height)' }, 119 | }, 120 | 'accordion-up': { 121 | from: { height: 'var(--radix-accordion-content-height)' }, 122 | to: { height: 0 }, 123 | }, 124 | }, 125 | animation: { 126 | 'accordion-down': 'accordion-down 0.2s ease-out', 127 | 'accordion-up': 'accordion-up 0.2s ease-out', 128 | }, 129 | boxShadow: { 130 | // light 131 | 'tremor-input': '0 1px 2px 0 rgb(0 0 0 / 0.05)', 132 | 'tremor-card': '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)', 133 | 'tremor-dropdown': '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)', 134 | // dark 135 | 'dark-tremor-input': '0 1px 2px 0 rgb(0 0 0 / 0.05)', 136 | 'dark-tremor-card': '0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)', 137 | 'dark-tremor-dropdown': '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)', 138 | }, 139 | borderRadius: { 140 | 'tremor-small': 'calc(var(--radius) - 4px)', 141 | 'tremor-default': `calc(var(--radius) - 2px)`, 142 | 'tremor-full': 'var(--radius)', 143 | }, 144 | fontSize: { 145 | 'tremor-label': ['0.75rem'], 146 | 'tremor-default': ['0.875rem', { lineHeight: '1.25rem' }], 147 | 'tremor-title': ['1.125rem', { lineHeight: '1.75rem' }], 148 | 'tremor-metric': ['1.875rem', { lineHeight: '2.25rem' }], 149 | }, 150 | }, 151 | }, 152 | safelist: [ 153 | { 154 | pattern: 155 | /^(bg-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, 156 | variants: ['hover', 'ui-selected'], 157 | }, 158 | { 159 | pattern: 160 | /^(text-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, 161 | variants: ['hover', 'ui-selected'], 162 | }, 163 | { 164 | pattern: 165 | /^(border-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, 166 | variants: ['hover', 'ui-selected'], 167 | }, 168 | { 169 | pattern: 170 | /^(ring-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, 171 | }, 172 | { 173 | pattern: 174 | /^(stroke-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, 175 | }, 176 | { 177 | pattern: 178 | /^(fill-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950))$/, 179 | }, 180 | ], 181 | plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography'), require('@tailwindcss/forms')], 182 | } 183 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/strict", 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "paths": { 6 | "@/*": ["src/*"] 7 | }, 8 | "jsx": "react-jsx", 9 | "jsxImportSource": "react" 10 | } 11 | } 12 | --------------------------------------------------------------------------------