├── .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 |
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 |
22 |
23 |
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 | setSidebarOpen(false)}>
43 | Close sidebar
44 |
45 |
46 |
47 |
48 |
49 |
50 |
65 |
66 |
135 |
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 |
setSidebarOpen(true)}>
16 | Open sidebar
17 |
18 |
19 |
20 | {/* Separator */}
21 |
22 |
23 |
24 |
40 |
41 |
42 | View notifications
43 |
44 |
45 |
46 | {/* Separator */}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
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 |
19 | Alex Whitmore
20 |
21 |
22 |
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 |
10 |
13 |
14 |
15 |
16 |
17 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/src/components/DashboardStats.astro:
--------------------------------------------------------------------------------
1 | ---
2 | import { PieChart } from './PieChart'
3 | import { BarLists } from './BarLists'
4 | import { AreaCharts } from './AreaCharts'
5 | ---
6 |
7 |
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 |
15 |
16 |
24 |
25 |
26 |
27 |
28 |
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 |
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 |
105 | Settings Icon
106 |
111 |
116 |
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 |
240 |
241 |
242 | ),
243 | GitHub: ({ ...props }) => (
244 |
245 |
250 |
251 | ),
252 | Google: ({ ...props }) => (
253 |
261 |
262 |
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 | setMobileMenuOpen(true)}>
16 | Open main menu
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
34 |
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 |
10 |
16 |
17 |
26 |
33 |
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 |
64 |
65 |
66 |
67 |
70 |
71 | Or continue with
72 |
73 |
74 |
75 |
76 |
83 |
84 |
85 |
86 |
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 |
10 |
16 |
17 |
18 | Open main menu
19 |
27 |
28 |
29 |
30 |
31 |
40 |
43 |
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 |
64 |
65 |
66 |
67 |
70 |
71 | Or continue with
72 |
73 |
74 |
75 |
76 |
83 |
84 |
85 |
86 |
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 |
57 |
61 |
62 |
63 | resume_back_end_developer.pdf
64 | 2.4mb
65 |
66 |
67 |
70 |
71 |
72 |
73 |
79 |
83 |
84 |
85 | coverletter_back_end_developer.pdf
86 | 4.5mb
87 |
88 |
89 |
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 |
114 |
115 |
116 |
117 |
118 |
Change password
119 |
Update your password associated with your account.
122 |
123 |
124 |
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 |
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 |
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 | Member
72 | Email
73 | Phone Number
74 | Manager
77 |
78 |
79 |
80 |
81 |
82 |
83 |
88 |
Michael Foster
89 |
90 |
91 |
92 |
93 |
michael.foster@dashboard.com
94 |
95 |
96 | 123-456-7890
97 |
98 | John Doe
99 |
100 |
101 |
102 |
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 |
--------------------------------------------------------------------------------