mirror of
https://github.com/tangledup-ai/stepfun-vector-stores-admin.git
synced 2026-01-20 19:00:59 +08:00
feat(clerk): add Clerk for auth and protected route (#146)
* feat: implement clerk auth * refactor: extract authenticated route layout * fix: add signUpFallbackRedirectUrl in ClerkProvider * fix: add default email address in clerk sign in * feat: add user-management flow with Clerk auth * refactor: extract learn-more component * chore: add learn more button in clerk auth layout * fix: update nav title for Clerk * fix: add example env file * feat(clerk): add fallback UI when publishable key is missing * docs(clerk): add clerk in readme
This commit is contained in:
1
.env.example
Normal file
1
.env.example
Normal file
@@ -0,0 +1 @@
|
||||
VITE_CLERK_PUBLISHABLE_KEY=
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,6 +12,8 @@ dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
.env
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
|
||||
12
README.md
12
README.md
@@ -32,6 +32,8 @@ I've been creating dashboard UIs at work and for my personal projects. I always
|
||||
|
||||
**Icons:** [Tabler Icons](https://tabler.io/icons)
|
||||
|
||||
**Auth (partial):** [Clerk](https://go.clerk.com/GttUAaK)
|
||||
|
||||
## Run Locally
|
||||
|
||||
Clone the project
|
||||
@@ -58,6 +60,16 @@ Start the server
|
||||
pnpm run dev
|
||||
```
|
||||
|
||||
## Sponsoring this project ❤️
|
||||
|
||||
If you find this project helpful or use this in your own work, consider [sponsoring me](https://github.com/sponsors/satnaing) to support development and maintenance. You can [buy me a coffee](https://buymeacoffee.com/satnaing) as well. Don’t worry, every penny helps. Thank you! 🙏
|
||||
|
||||
For questions or sponsorship inquiries, feel free to reach out at [contact@satnaing.dev](mailto:contact@satnaing.dev).
|
||||
|
||||
### Current Sponsor
|
||||
|
||||
- [Clerk](https://go.clerk.com/GttUAaK) - for backing the implementation of Clerk in this project
|
||||
|
||||
## Author
|
||||
|
||||
Crafted with 🤍 by [@satnaing](https://github.com/satnaing)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"knip": "knip"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clerk/clerk-react": "^5.31.4",
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.7",
|
||||
"@radix-ui/react-avatar": "^1.1.4",
|
||||
|
||||
77
pnpm-lock.yaml
generated
77
pnpm-lock.yaml
generated
@@ -8,6 +8,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@clerk/clerk-react':
|
||||
specifier: ^5.31.4
|
||||
version: 5.31.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@hookform/resolvers':
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.1(react-hook-form@7.55.0(react@19.1.0))
|
||||
@@ -294,6 +297,29 @@ packages:
|
||||
resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@clerk/clerk-react@5.31.4':
|
||||
resolution: {integrity: sha512-VtjOEzq/ncwHRn23xhmy4DRefrrSeUkKHiB/EighusYVkjmpzWMXYGD9Wdd79hwUhJesUsBiQdZhE5qkJ+mnJA==}
|
||||
engines: {node: '>=18.17.0'}
|
||||
peerDependencies:
|
||||
react: ^18.0.0 || ^19.0.0 || ^19.0.0-0
|
||||
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-0
|
||||
|
||||
'@clerk/shared@3.9.1':
|
||||
resolution: {integrity: sha512-Gw7yPaas3lv+pkkbBwuqVVtWVH1nZl1hF8kVvdEhPALOkf6ww6azL6qcHaiFUHCW+iult3flJjplduFrfWF50g==}
|
||||
engines: {node: '>=18.17.0'}
|
||||
peerDependencies:
|
||||
react: ^18.0.0 || ^19.0.0 || ^19.0.0-0
|
||||
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-0
|
||||
peerDependenciesMeta:
|
||||
react:
|
||||
optional: true
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
'@clerk/types@4.59.0':
|
||||
resolution: {integrity: sha512-VZ61lDWoz9cWTlSpO1KMGq7utl96ZuSBIOpM6togxYTp+TG0kD6QEJVinMaJLREtx8jRvXpMG7ZzBLE3zy0GSA==}
|
||||
engines: {node: '>=18.17.0'}
|
||||
|
||||
'@date-fns/tz@1.2.0':
|
||||
resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
|
||||
|
||||
@@ -1761,6 +1787,10 @@ packages:
|
||||
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
dequal@2.0.3:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
detect-libc@2.0.3:
|
||||
resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1961,6 +1991,9 @@ packages:
|
||||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
glob-to-regexp@0.4.1:
|
||||
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
|
||||
|
||||
globals@11.12.0:
|
||||
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -2517,6 +2550,9 @@ packages:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
std-env@3.9.0:
|
||||
resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
|
||||
|
||||
strip-json-comments@3.1.1:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2529,6 +2565,11 @@ packages:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
swr@2.3.3:
|
||||
resolution: {integrity: sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==}
|
||||
peerDependencies:
|
||||
react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||
|
||||
tailwind-merge@3.2.0:
|
||||
resolution: {integrity: sha512-FQT/OVqCD+7edmmJpsgCsY820RTD5AkBryuG5IUqR5YQZSdj5xlH5nLgH7YPths7WsLPSpSBNneJdM8aS8aeFA==}
|
||||
|
||||
@@ -2836,6 +2877,30 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.25.9
|
||||
'@babel/helper-validator-identifier': 7.25.9
|
||||
|
||||
'@clerk/clerk-react@5.31.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@clerk/shared': 3.9.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@clerk/types': 4.59.0
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
tslib: 2.8.1
|
||||
|
||||
'@clerk/shared@3.9.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
'@clerk/types': 4.59.0
|
||||
dequal: 2.0.3
|
||||
glob-to-regexp: 0.4.1
|
||||
js-cookie: 3.0.5
|
||||
std-env: 3.9.0
|
||||
swr: 2.3.3(react@19.1.0)
|
||||
optionalDependencies:
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
|
||||
'@clerk/types@4.59.0':
|
||||
dependencies:
|
||||
csstype: 3.1.3
|
||||
|
||||
'@date-fns/tz@1.2.0': {}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.3':
|
||||
@@ -4200,6 +4265,8 @@ snapshots:
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
dequal@2.0.3: {}
|
||||
|
||||
detect-libc@2.0.3: {}
|
||||
|
||||
detect-node-es@1.1.0: {}
|
||||
@@ -4444,6 +4511,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
|
||||
glob-to-regexp@0.4.1: {}
|
||||
|
||||
globals@11.12.0: {}
|
||||
|
||||
globals@14.0.0: {}
|
||||
@@ -4881,6 +4950,8 @@ snapshots:
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
std-env@3.9.0: {}
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
strip-json-comments@5.0.1: {}
|
||||
@@ -4889,6 +4960,12 @@ snapshots:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
swr@2.3.3(react@19.1.0):
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
react: 19.1.0
|
||||
use-sync-external-store: 1.5.0(react@19.1.0)
|
||||
|
||||
tailwind-merge@3.2.0: {}
|
||||
|
||||
tailwindcss@4.1.4: {}
|
||||
|
||||
41
src/assets/clerk-full-logo.tsx
Normal file
41
src/assets/clerk-full-logo.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { SVGProps } from 'react'
|
||||
|
||||
export function ClerkFullLogo(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
width={77}
|
||||
height={24}
|
||||
viewBox='0 0 77 24'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d='M35.148 16.738a4.198 4.198 0 01-3.06 1.283 3.53 3.53 0 01-2.604-1.034c-.619-.645-.975-1.566-.975-2.665 0-2.199 1.432-3.703 3.58-3.703a3.914 3.914 0 013.034 1.377l1.859-1.644c-1.211-1.47-3.176-2.229-5.042-2.229-3.652 0-6.24 2.517-6.24 6.22 0 1.831.643 3.374 1.728 4.463s2.631 1.728 4.415 1.728c2.317 0 4.166-.94 5.203-2.122l-1.898-1.674zM38.727 3.428h2.766V20.34h-2.766V3.428zM54.818 15.283c.046-.368.07-.74.076-1.11 0-3.507-2.296-6.047-5.847-6.047a5.738 5.738 0 00-4.215 1.725c-1.038 1.089-1.66 2.631-1.66 4.47 0 3.749 2.642 6.216 6.146 6.216 2.35 0 4.043-.951 5.058-2.242l-1.812-1.605-.09-.076a3.749 3.749 0 01-3.008 1.406c-1.778 0-3.061-1.037-3.427-2.737h8.779zm-8.733-2.22a3.365 3.365 0 01.737-1.449 3.082 3.082 0 012.368-.996c1.58 0 2.57.988 2.911 2.445h-6.016zM63.445 8.09v3.084a13.36 13.36 0 00-.838-.05c-2.094 0-3.282 1.505-3.282 3.479v5.736h-2.763V8.261h2.763v1.83h.025c.938-1.283 2.284-1.997 3.75-1.997l.345-.004zM69.887 15.281l-1.998 2.222v2.837h-2.764V3.428h2.764v10.374L72.822 8.3h3.283l-4.341 4.86 4.417 7.18h-3.11l-3.133-5.059h-.051z'
|
||||
fill='#1F0256'
|
||||
/>
|
||||
<path
|
||||
d='M19.116 3.16l-2.88 2.881a.571.571 0 01-.701.084 6.854 6.854 0 00-10.39 5.647 6.867 6.867 0 00.979 3.764.571.571 0 01-.084.699l-2.88 2.88a.57.57 0 01-.865-.063A11.994 11.994 0 0119.051 2.295a.57.57 0 01.065.866z'
|
||||
fill='url(#paint0_linear_26568_214324)'
|
||||
/>
|
||||
<path
|
||||
d='M19.113 20.829l-2.88-2.88a.571.571 0 00-.7-.085 6.854 6.854 0 01-7.081 0 .571.571 0 00-.7.084l-2.881 2.88a.57.57 0 00.062.877 11.994 11.994 0 0014.114 0 .571.571 0 00.066-.876zM11.997 15.422a3.427 3.427 0 100-6.854 3.427 3.427 0 000 6.854z'
|
||||
fill='#1F0256'
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id='paint0_linear_26568_214324'
|
||||
x1={16.4087}
|
||||
y1={-1.75881}
|
||||
x2={-7.88473}
|
||||
y2={22.5365}
|
||||
gradientUnits='userSpaceOnUse'
|
||||
>
|
||||
<stop stopColor='#17CCFC' />
|
||||
<stop offset={0.5} stopColor='#5D31FF' />
|
||||
<stop offset={1} stopColor='#F35AFF' />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
23
src/assets/clerk-logo.tsx
Normal file
23
src/assets/clerk-logo.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { SVGProps } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export function ClerkLogo({ className, ...props }: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
role='img'
|
||||
viewBox='0 0 24 24'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
id='clerk'
|
||||
height='24'
|
||||
width='24'
|
||||
className={cn('[&>path]:fill-foreground', className)}
|
||||
{...props}
|
||||
>
|
||||
<title>Clerk</title>
|
||||
<path
|
||||
d='m21.47 20.829 -2.881 -2.881a0.572 0.572 0 0 0 -0.7 -0.084 6.854 6.854 0 0 1 -7.081 0 0.576 0.576 0 0 0 -0.7 0.084l-2.881 2.881a0.576 0.576 0 0 0 -0.103 0.69 0.57 0.57 0 0 0 0.166 0.186 12 12 0 0 0 14.113 0 0.58 0.58 0 0 0 0.239 -0.423 0.576 0.576 0 0 0 -0.172 -0.453Zm0.002 -17.668 -2.88 2.88a0.569 0.569 0 0 1 -0.701 0.084A6.857 6.857 0 0 0 8.724 8.08a6.862 6.862 0 0 0 -1.222 3.692 6.86 6.86 0 0 0 0.978 3.764 0.573 0.573 0 0 1 -0.083 0.699l-2.881 2.88a0.567 0.567 0 0 1 -0.864 -0.063A11.993 11.993 0 0 1 6.771 2.7a11.99 11.99 0 0 1 14.637 -0.405 0.566 0.566 0 0 1 0.232 0.418 0.57 0.57 0 0 1 -0.168 0.448Zm-7.118 12.261a3.427 3.427 0 1 0 0 -6.854 3.427 3.427 0 0 0 0 6.854Z'
|
||||
strokeWidth='1'
|
||||
></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
37
src/components/layout/authenticated-layout.tsx
Normal file
37
src/components/layout/authenticated-layout.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import Cookies from 'js-cookie'
|
||||
import { Outlet } from '@tanstack/react-router'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SearchProvider } from '@/context/search-context'
|
||||
import { SidebarProvider } from '@/components/ui/sidebar'
|
||||
import { AppSidebar } from '@/components/layout/app-sidebar'
|
||||
import SkipToMain from '@/components/skip-to-main'
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
export function AuthenticatedLayout({ children }: Props) {
|
||||
const defaultOpen = Cookies.get('sidebar_state') !== 'false'
|
||||
return (
|
||||
<SearchProvider>
|
||||
<SidebarProvider defaultOpen={defaultOpen}>
|
||||
<SkipToMain />
|
||||
<AppSidebar />
|
||||
<div
|
||||
id='content'
|
||||
className={cn(
|
||||
'ml-auto w-full max-w-full',
|
||||
'peer-data-[state=collapsed]:w-[calc(100%-var(--sidebar-width-icon)-1rem)]',
|
||||
'peer-data-[state=expanded]:w-[calc(100%-var(--sidebar-width))]',
|
||||
'sm:transition-[width] sm:duration-200 sm:ease-linear',
|
||||
'flex h-svh flex-col',
|
||||
'group-data-[scroll-locked=1]/body:h-full',
|
||||
'has-[main.fixed-main]:group-data-[scroll-locked=1]/body:h-svh'
|
||||
)}
|
||||
>
|
||||
{children ? children : <Outlet />}
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
</SearchProvider>
|
||||
)
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
IconUsers,
|
||||
} from '@tabler/icons-react'
|
||||
import { AudioWaveform, Command, GalleryVerticalEnd } from 'lucide-react'
|
||||
import { ClerkLogo } from '@/assets/clerk-logo'
|
||||
import { type SidebarData } from '../types'
|
||||
|
||||
export const sidebarData: SidebarData = {
|
||||
@@ -75,6 +76,24 @@ export const sidebarData: SidebarData = {
|
||||
url: '/users',
|
||||
icon: IconUsers,
|
||||
},
|
||||
{
|
||||
title: 'Secured by Clerk',
|
||||
icon: ClerkLogo,
|
||||
items: [
|
||||
{
|
||||
title: 'Sign In',
|
||||
url: '/clerk/sign-in',
|
||||
},
|
||||
{
|
||||
title: 'Sign Up',
|
||||
url: '/clerk/sign-up',
|
||||
},
|
||||
{
|
||||
title: 'User Management',
|
||||
url: '/clerk/user-management',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,13 +6,14 @@ interface MainProps extends React.HTMLAttributes<HTMLElement> {
|
||||
ref?: React.Ref<HTMLElement>
|
||||
}
|
||||
|
||||
export const Main = ({ fixed, ...props }: MainProps) => {
|
||||
export const Main = ({ fixed, className, ...props }: MainProps) => {
|
||||
return (
|
||||
<main
|
||||
className={cn(
|
||||
'peer-[.header-fixed]/header:mt-16',
|
||||
'px-4 py-6',
|
||||
fixed && 'fixed-main flex grow flex-col overflow-hidden'
|
||||
fixed && 'fixed-main flex grow flex-col overflow-hidden',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
||||
44
src/components/learn-more.tsx
Normal file
44
src/components/learn-more.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Root, Content, Trigger } from '@radix-ui/react-popover'
|
||||
import { IconQuestionMark } from '@tabler/icons-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover'
|
||||
|
||||
interface Props extends React.ComponentProps<typeof Root> {
|
||||
contentProps?: React.ComponentProps<typeof Content>
|
||||
triggerProps?: React.ComponentProps<typeof Trigger>
|
||||
}
|
||||
|
||||
export function LearnMore({
|
||||
children,
|
||||
contentProps,
|
||||
triggerProps,
|
||||
...props
|
||||
}: Props) {
|
||||
return (
|
||||
<Popover {...props}>
|
||||
<PopoverTrigger
|
||||
asChild
|
||||
{...triggerProps}
|
||||
className={cn('size-5 rounded-full', triggerProps?.className)}
|
||||
>
|
||||
<Button variant='outline' size='icon'>
|
||||
<span className='sr-only'>Learn more</span>
|
||||
<IconQuestionMark className='size-3' />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
side='top'
|
||||
align='start'
|
||||
{...contentProps}
|
||||
className={cn('text-muted-foreground text-sm', contentProps?.className)}
|
||||
>
|
||||
{children}
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
// Import Routes
|
||||
|
||||
import { Route as rootRoute } from './routes/__root'
|
||||
import { Route as ClerkRouteImport } from './routes/clerk/route'
|
||||
import { Route as AuthenticatedRouteImport } from './routes/_authenticated/route'
|
||||
import { Route as AuthenticatedIndexImport } from './routes/_authenticated/index'
|
||||
import { Route as errors503Import } from './routes/(errors)/503'
|
||||
@@ -23,6 +24,8 @@ import { Route as authSignIn2Import } from './routes/(auth)/sign-in-2'
|
||||
import { Route as authSignInImport } from './routes/(auth)/sign-in'
|
||||
import { Route as authOtpImport } from './routes/(auth)/otp'
|
||||
import { Route as authForgotPasswordImport } from './routes/(auth)/forgot-password'
|
||||
import { Route as ClerkAuthenticatedRouteImport } from './routes/clerk/_authenticated/route'
|
||||
import { Route as ClerkauthRouteImport } from './routes/clerk/(auth)/route'
|
||||
import { Route as AuthenticatedSettingsRouteImport } from './routes/_authenticated/settings/route'
|
||||
import { Route as AuthenticatedUsersIndexImport } from './routes/_authenticated/users/index'
|
||||
import { Route as AuthenticatedTasksIndexImport } from './routes/_authenticated/tasks/index'
|
||||
@@ -30,6 +33,9 @@ import { Route as AuthenticatedSettingsIndexImport } from './routes/_authenticat
|
||||
import { Route as AuthenticatedHelpCenterIndexImport } from './routes/_authenticated/help-center/index'
|
||||
import { Route as AuthenticatedChatsIndexImport } from './routes/_authenticated/chats/index'
|
||||
import { Route as AuthenticatedAppsIndexImport } from './routes/_authenticated/apps/index'
|
||||
import { Route as ClerkAuthenticatedUserManagementImport } from './routes/clerk/_authenticated/user-management'
|
||||
import { Route as ClerkauthSignUpImport } from './routes/clerk/(auth)/sign-up'
|
||||
import { Route as ClerkauthSignInImport } from './routes/clerk/(auth)/sign-in'
|
||||
import { Route as AuthenticatedSettingsNotificationsImport } from './routes/_authenticated/settings/notifications'
|
||||
import { Route as AuthenticatedSettingsDisplayImport } from './routes/_authenticated/settings/display'
|
||||
import { Route as AuthenticatedSettingsAppearanceImport } from './routes/_authenticated/settings/appearance'
|
||||
@@ -37,6 +43,12 @@ import { Route as AuthenticatedSettingsAccountImport } from './routes/_authentic
|
||||
|
||||
// Create/Update Routes
|
||||
|
||||
const ClerkRouteRoute = ClerkRouteImport.update({
|
||||
id: '/clerk',
|
||||
path: '/clerk',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const AuthenticatedRouteRoute = AuthenticatedRouteImport.update({
|
||||
id: '/_authenticated',
|
||||
getParentRoute: () => rootRoute,
|
||||
@@ -108,6 +120,16 @@ const authForgotPasswordRoute = authForgotPasswordImport.update({
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const ClerkAuthenticatedRouteRoute = ClerkAuthenticatedRouteImport.update({
|
||||
id: '/_authenticated',
|
||||
getParentRoute: () => ClerkRouteRoute,
|
||||
} as any)
|
||||
|
||||
const ClerkauthRouteRoute = ClerkauthRouteImport.update({
|
||||
id: '/(auth)',
|
||||
getParentRoute: () => ClerkRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AuthenticatedSettingsRouteRoute = AuthenticatedSettingsRouteImport.update(
|
||||
{
|
||||
id: '/settings',
|
||||
@@ -155,6 +177,25 @@ const AuthenticatedAppsIndexRoute = AuthenticatedAppsIndexImport.update({
|
||||
getParentRoute: () => AuthenticatedRouteRoute,
|
||||
} as any)
|
||||
|
||||
const ClerkAuthenticatedUserManagementRoute =
|
||||
ClerkAuthenticatedUserManagementImport.update({
|
||||
id: '/user-management',
|
||||
path: '/user-management',
|
||||
getParentRoute: () => ClerkAuthenticatedRouteRoute,
|
||||
} as any)
|
||||
|
||||
const ClerkauthSignUpRoute = ClerkauthSignUpImport.update({
|
||||
id: '/sign-up',
|
||||
path: '/sign-up',
|
||||
getParentRoute: () => ClerkauthRouteRoute,
|
||||
} as any)
|
||||
|
||||
const ClerkauthSignInRoute = ClerkauthSignInImport.update({
|
||||
id: '/sign-in',
|
||||
path: '/sign-in',
|
||||
getParentRoute: () => ClerkauthRouteRoute,
|
||||
} as any)
|
||||
|
||||
const AuthenticatedSettingsNotificationsRoute =
|
||||
AuthenticatedSettingsNotificationsImport.update({
|
||||
id: '/notifications',
|
||||
@@ -194,6 +235,13 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AuthenticatedRouteImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/clerk': {
|
||||
id: '/clerk'
|
||||
path: '/clerk'
|
||||
fullPath: '/clerk'
|
||||
preLoaderRoute: typeof ClerkRouteImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/_authenticated/settings': {
|
||||
id: '/_authenticated/settings'
|
||||
path: '/settings'
|
||||
@@ -201,6 +249,20 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AuthenticatedSettingsRouteImport
|
||||
parentRoute: typeof AuthenticatedRouteImport
|
||||
}
|
||||
'/clerk/(auth)': {
|
||||
id: '/clerk/(auth)'
|
||||
path: '/'
|
||||
fullPath: '/clerk/'
|
||||
preLoaderRoute: typeof ClerkauthRouteImport
|
||||
parentRoute: typeof ClerkRouteImport
|
||||
}
|
||||
'/clerk/_authenticated': {
|
||||
id: '/clerk/_authenticated'
|
||||
path: ''
|
||||
fullPath: '/clerk'
|
||||
preLoaderRoute: typeof ClerkAuthenticatedRouteImport
|
||||
parentRoute: typeof ClerkRouteImport
|
||||
}
|
||||
'/(auth)/forgot-password': {
|
||||
id: '/(auth)/forgot-password'
|
||||
path: '/forgot-password'
|
||||
@@ -306,6 +368,27 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof AuthenticatedSettingsNotificationsImport
|
||||
parentRoute: typeof AuthenticatedSettingsRouteImport
|
||||
}
|
||||
'/clerk/(auth)/sign-in': {
|
||||
id: '/clerk/(auth)/sign-in'
|
||||
path: '/sign-in'
|
||||
fullPath: '/clerk/sign-in'
|
||||
preLoaderRoute: typeof ClerkauthSignInImport
|
||||
parentRoute: typeof ClerkauthRouteImport
|
||||
}
|
||||
'/clerk/(auth)/sign-up': {
|
||||
id: '/clerk/(auth)/sign-up'
|
||||
path: '/sign-up'
|
||||
fullPath: '/clerk/sign-up'
|
||||
preLoaderRoute: typeof ClerkauthSignUpImport
|
||||
parentRoute: typeof ClerkauthRouteImport
|
||||
}
|
||||
'/clerk/_authenticated/user-management': {
|
||||
id: '/clerk/_authenticated/user-management'
|
||||
path: '/user-management'
|
||||
fullPath: '/clerk/user-management'
|
||||
preLoaderRoute: typeof ClerkAuthenticatedUserManagementImport
|
||||
parentRoute: typeof ClerkAuthenticatedRouteImport
|
||||
}
|
||||
'/_authenticated/apps/': {
|
||||
id: '/_authenticated/apps/'
|
||||
path: '/apps'
|
||||
@@ -399,9 +482,54 @@ const AuthenticatedRouteRouteChildren: AuthenticatedRouteRouteChildren = {
|
||||
const AuthenticatedRouteRouteWithChildren =
|
||||
AuthenticatedRouteRoute._addFileChildren(AuthenticatedRouteRouteChildren)
|
||||
|
||||
interface ClerkauthRouteRouteChildren {
|
||||
ClerkauthSignInRoute: typeof ClerkauthSignInRoute
|
||||
ClerkauthSignUpRoute: typeof ClerkauthSignUpRoute
|
||||
}
|
||||
|
||||
const ClerkauthRouteRouteChildren: ClerkauthRouteRouteChildren = {
|
||||
ClerkauthSignInRoute: ClerkauthSignInRoute,
|
||||
ClerkauthSignUpRoute: ClerkauthSignUpRoute,
|
||||
}
|
||||
|
||||
const ClerkauthRouteRouteWithChildren = ClerkauthRouteRoute._addFileChildren(
|
||||
ClerkauthRouteRouteChildren,
|
||||
)
|
||||
|
||||
interface ClerkAuthenticatedRouteRouteChildren {
|
||||
ClerkAuthenticatedUserManagementRoute: typeof ClerkAuthenticatedUserManagementRoute
|
||||
}
|
||||
|
||||
const ClerkAuthenticatedRouteRouteChildren: ClerkAuthenticatedRouteRouteChildren =
|
||||
{
|
||||
ClerkAuthenticatedUserManagementRoute:
|
||||
ClerkAuthenticatedUserManagementRoute,
|
||||
}
|
||||
|
||||
const ClerkAuthenticatedRouteRouteWithChildren =
|
||||
ClerkAuthenticatedRouteRoute._addFileChildren(
|
||||
ClerkAuthenticatedRouteRouteChildren,
|
||||
)
|
||||
|
||||
interface ClerkRouteRouteChildren {
|
||||
ClerkauthRouteRoute: typeof ClerkauthRouteRouteWithChildren
|
||||
ClerkAuthenticatedRouteRoute: typeof ClerkAuthenticatedRouteRouteWithChildren
|
||||
}
|
||||
|
||||
const ClerkRouteRouteChildren: ClerkRouteRouteChildren = {
|
||||
ClerkauthRouteRoute: ClerkauthRouteRouteWithChildren,
|
||||
ClerkAuthenticatedRouteRoute: ClerkAuthenticatedRouteRouteWithChildren,
|
||||
}
|
||||
|
||||
const ClerkRouteRouteWithChildren = ClerkRouteRoute._addFileChildren(
|
||||
ClerkRouteRouteChildren,
|
||||
)
|
||||
|
||||
export interface FileRoutesByFullPath {
|
||||
'': typeof AuthenticatedRouteRouteWithChildren
|
||||
'/clerk': typeof ClerkAuthenticatedRouteRouteWithChildren
|
||||
'/settings': typeof AuthenticatedSettingsRouteRouteWithChildren
|
||||
'/clerk/': typeof ClerkauthRouteRouteWithChildren
|
||||
'/forgot-password': typeof authForgotPasswordRoute
|
||||
'/otp': typeof authOtpRoute
|
||||
'/sign-in': typeof authSignInRoute
|
||||
@@ -417,6 +545,9 @@ export interface FileRoutesByFullPath {
|
||||
'/settings/appearance': typeof AuthenticatedSettingsAppearanceRoute
|
||||
'/settings/display': typeof AuthenticatedSettingsDisplayRoute
|
||||
'/settings/notifications': typeof AuthenticatedSettingsNotificationsRoute
|
||||
'/clerk/sign-in': typeof ClerkauthSignInRoute
|
||||
'/clerk/sign-up': typeof ClerkauthSignUpRoute
|
||||
'/clerk/user-management': typeof ClerkAuthenticatedUserManagementRoute
|
||||
'/apps': typeof AuthenticatedAppsIndexRoute
|
||||
'/chats': typeof AuthenticatedChatsIndexRoute
|
||||
'/help-center': typeof AuthenticatedHelpCenterIndexRoute
|
||||
@@ -426,6 +557,7 @@ export interface FileRoutesByFullPath {
|
||||
}
|
||||
|
||||
export interface FileRoutesByTo {
|
||||
'/clerk': typeof ClerkAuthenticatedRouteRouteWithChildren
|
||||
'/forgot-password': typeof authForgotPasswordRoute
|
||||
'/otp': typeof authOtpRoute
|
||||
'/sign-in': typeof authSignInRoute
|
||||
@@ -441,6 +573,9 @@ export interface FileRoutesByTo {
|
||||
'/settings/appearance': typeof AuthenticatedSettingsAppearanceRoute
|
||||
'/settings/display': typeof AuthenticatedSettingsDisplayRoute
|
||||
'/settings/notifications': typeof AuthenticatedSettingsNotificationsRoute
|
||||
'/clerk/sign-in': typeof ClerkauthSignInRoute
|
||||
'/clerk/sign-up': typeof ClerkauthSignUpRoute
|
||||
'/clerk/user-management': typeof ClerkAuthenticatedUserManagementRoute
|
||||
'/apps': typeof AuthenticatedAppsIndexRoute
|
||||
'/chats': typeof AuthenticatedChatsIndexRoute
|
||||
'/help-center': typeof AuthenticatedHelpCenterIndexRoute
|
||||
@@ -452,7 +587,10 @@ export interface FileRoutesByTo {
|
||||
export interface FileRoutesById {
|
||||
__root__: typeof rootRoute
|
||||
'/_authenticated': typeof AuthenticatedRouteRouteWithChildren
|
||||
'/clerk': typeof ClerkRouteRouteWithChildren
|
||||
'/_authenticated/settings': typeof AuthenticatedSettingsRouteRouteWithChildren
|
||||
'/clerk/(auth)': typeof ClerkauthRouteRouteWithChildren
|
||||
'/clerk/_authenticated': typeof ClerkAuthenticatedRouteRouteWithChildren
|
||||
'/(auth)/forgot-password': typeof authForgotPasswordRoute
|
||||
'/(auth)/otp': typeof authOtpRoute
|
||||
'/(auth)/sign-in': typeof authSignInRoute
|
||||
@@ -468,6 +606,9 @@ export interface FileRoutesById {
|
||||
'/_authenticated/settings/appearance': typeof AuthenticatedSettingsAppearanceRoute
|
||||
'/_authenticated/settings/display': typeof AuthenticatedSettingsDisplayRoute
|
||||
'/_authenticated/settings/notifications': typeof AuthenticatedSettingsNotificationsRoute
|
||||
'/clerk/(auth)/sign-in': typeof ClerkauthSignInRoute
|
||||
'/clerk/(auth)/sign-up': typeof ClerkauthSignUpRoute
|
||||
'/clerk/_authenticated/user-management': typeof ClerkAuthenticatedUserManagementRoute
|
||||
'/_authenticated/apps/': typeof AuthenticatedAppsIndexRoute
|
||||
'/_authenticated/chats/': typeof AuthenticatedChatsIndexRoute
|
||||
'/_authenticated/help-center/': typeof AuthenticatedHelpCenterIndexRoute
|
||||
@@ -480,7 +621,9 @@ export interface FileRouteTypes {
|
||||
fileRoutesByFullPath: FileRoutesByFullPath
|
||||
fullPaths:
|
||||
| ''
|
||||
| '/clerk'
|
||||
| '/settings'
|
||||
| '/clerk/'
|
||||
| '/forgot-password'
|
||||
| '/otp'
|
||||
| '/sign-in'
|
||||
@@ -496,6 +639,9 @@ export interface FileRouteTypes {
|
||||
| '/settings/appearance'
|
||||
| '/settings/display'
|
||||
| '/settings/notifications'
|
||||
| '/clerk/sign-in'
|
||||
| '/clerk/sign-up'
|
||||
| '/clerk/user-management'
|
||||
| '/apps'
|
||||
| '/chats'
|
||||
| '/help-center'
|
||||
@@ -504,6 +650,7 @@ export interface FileRouteTypes {
|
||||
| '/users'
|
||||
fileRoutesByTo: FileRoutesByTo
|
||||
to:
|
||||
| '/clerk'
|
||||
| '/forgot-password'
|
||||
| '/otp'
|
||||
| '/sign-in'
|
||||
@@ -519,6 +666,9 @@ export interface FileRouteTypes {
|
||||
| '/settings/appearance'
|
||||
| '/settings/display'
|
||||
| '/settings/notifications'
|
||||
| '/clerk/sign-in'
|
||||
| '/clerk/sign-up'
|
||||
| '/clerk/user-management'
|
||||
| '/apps'
|
||||
| '/chats'
|
||||
| '/help-center'
|
||||
@@ -528,7 +678,10 @@ export interface FileRouteTypes {
|
||||
id:
|
||||
| '__root__'
|
||||
| '/_authenticated'
|
||||
| '/clerk'
|
||||
| '/_authenticated/settings'
|
||||
| '/clerk/(auth)'
|
||||
| '/clerk/_authenticated'
|
||||
| '/(auth)/forgot-password'
|
||||
| '/(auth)/otp'
|
||||
| '/(auth)/sign-in'
|
||||
@@ -544,6 +697,9 @@ export interface FileRouteTypes {
|
||||
| '/_authenticated/settings/appearance'
|
||||
| '/_authenticated/settings/display'
|
||||
| '/_authenticated/settings/notifications'
|
||||
| '/clerk/(auth)/sign-in'
|
||||
| '/clerk/(auth)/sign-up'
|
||||
| '/clerk/_authenticated/user-management'
|
||||
| '/_authenticated/apps/'
|
||||
| '/_authenticated/chats/'
|
||||
| '/_authenticated/help-center/'
|
||||
@@ -555,6 +711,7 @@ export interface FileRouteTypes {
|
||||
|
||||
export interface RootRouteChildren {
|
||||
AuthenticatedRouteRoute: typeof AuthenticatedRouteRouteWithChildren
|
||||
ClerkRouteRoute: typeof ClerkRouteRouteWithChildren
|
||||
authForgotPasswordRoute: typeof authForgotPasswordRoute
|
||||
authOtpRoute: typeof authOtpRoute
|
||||
authSignInRoute: typeof authSignInRoute
|
||||
@@ -569,6 +726,7 @@ export interface RootRouteChildren {
|
||||
|
||||
const rootRouteChildren: RootRouteChildren = {
|
||||
AuthenticatedRouteRoute: AuthenticatedRouteRouteWithChildren,
|
||||
ClerkRouteRoute: ClerkRouteRouteWithChildren,
|
||||
authForgotPasswordRoute: authForgotPasswordRoute,
|
||||
authOtpRoute: authOtpRoute,
|
||||
authSignInRoute: authSignInRoute,
|
||||
@@ -592,6 +750,7 @@ export const routeTree = rootRoute
|
||||
"filePath": "__root.tsx",
|
||||
"children": [
|
||||
"/_authenticated",
|
||||
"/clerk",
|
||||
"/(auth)/forgot-password",
|
||||
"/(auth)/otp",
|
||||
"/(auth)/sign-in",
|
||||
@@ -616,6 +775,13 @@ export const routeTree = rootRoute
|
||||
"/_authenticated/users/"
|
||||
]
|
||||
},
|
||||
"/clerk": {
|
||||
"filePath": "clerk/route.tsx",
|
||||
"children": [
|
||||
"/clerk/(auth)",
|
||||
"/clerk/_authenticated"
|
||||
]
|
||||
},
|
||||
"/_authenticated/settings": {
|
||||
"filePath": "_authenticated/settings/route.tsx",
|
||||
"parent": "/_authenticated",
|
||||
@@ -627,6 +793,21 @@ export const routeTree = rootRoute
|
||||
"/_authenticated/settings/"
|
||||
]
|
||||
},
|
||||
"/clerk/(auth)": {
|
||||
"filePath": "clerk/(auth)/route.tsx",
|
||||
"parent": "/clerk",
|
||||
"children": [
|
||||
"/clerk/(auth)/sign-in",
|
||||
"/clerk/(auth)/sign-up"
|
||||
]
|
||||
},
|
||||
"/clerk/_authenticated": {
|
||||
"filePath": "clerk/_authenticated/route.tsx",
|
||||
"parent": "/clerk",
|
||||
"children": [
|
||||
"/clerk/_authenticated/user-management"
|
||||
]
|
||||
},
|
||||
"/(auth)/forgot-password": {
|
||||
"filePath": "(auth)/forgot-password.tsx"
|
||||
},
|
||||
@@ -677,6 +858,18 @@ export const routeTree = rootRoute
|
||||
"filePath": "_authenticated/settings/notifications.tsx",
|
||||
"parent": "/_authenticated/settings"
|
||||
},
|
||||
"/clerk/(auth)/sign-in": {
|
||||
"filePath": "clerk/(auth)/sign-in.tsx",
|
||||
"parent": "/clerk/(auth)"
|
||||
},
|
||||
"/clerk/(auth)/sign-up": {
|
||||
"filePath": "clerk/(auth)/sign-up.tsx",
|
||||
"parent": "/clerk/(auth)"
|
||||
},
|
||||
"/clerk/_authenticated/user-management": {
|
||||
"filePath": "clerk/_authenticated/user-management.tsx",
|
||||
"parent": "/clerk/_authenticated"
|
||||
},
|
||||
"/_authenticated/apps/": {
|
||||
"filePath": "_authenticated/apps/index.tsx",
|
||||
"parent": "/_authenticated"
|
||||
|
||||
@@ -1,37 +1,6 @@
|
||||
import Cookies from 'js-cookie'
|
||||
import { createFileRoute, Outlet } from '@tanstack/react-router'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SearchProvider } from '@/context/search-context'
|
||||
import { SidebarProvider } from '@/components/ui/sidebar'
|
||||
import { AppSidebar } from '@/components/layout/app-sidebar'
|
||||
import SkipToMain from '@/components/skip-to-main'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { AuthenticatedLayout } from '@/components/layout/authenticated-layout'
|
||||
|
||||
export const Route = createFileRoute('/_authenticated')({
|
||||
component: RouteComponent,
|
||||
component: AuthenticatedLayout,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
const defaultOpen = Cookies.get('sidebar_state') !== 'false'
|
||||
return (
|
||||
<SearchProvider>
|
||||
<SidebarProvider defaultOpen={defaultOpen}>
|
||||
<SkipToMain />
|
||||
<AppSidebar />
|
||||
<div
|
||||
id='content'
|
||||
className={cn(
|
||||
'ml-auto w-full max-w-full',
|
||||
'peer-data-[state=collapsed]:w-[calc(100%-var(--sidebar-width-icon)-1rem)]',
|
||||
'peer-data-[state=expanded]:w-[calc(100%-var(--sidebar-width))]',
|
||||
'sm:transition-[width] sm:duration-200 sm:ease-linear',
|
||||
'flex h-svh flex-col',
|
||||
'group-data-[scroll-locked=1]/body:h-full',
|
||||
'has-[main.fixed-main]:group-data-[scroll-locked=1]/body:h-svh'
|
||||
)}
|
||||
>
|
||||
<Outlet />
|
||||
</div>
|
||||
</SidebarProvider>
|
||||
</SearchProvider>
|
||||
)
|
||||
}
|
||||
|
||||
69
src/routes/clerk/(auth)/route.tsx
Normal file
69
src/routes/clerk/(auth)/route.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import { createFileRoute, Link, Outlet } from '@tanstack/react-router'
|
||||
import { ClerkFullLogo } from '@/assets/clerk-full-logo'
|
||||
import { LearnMore } from '@/components/learn-more'
|
||||
|
||||
export const Route = createFileRoute('/clerk/(auth)')({
|
||||
component: ClerkAuthLayout,
|
||||
})
|
||||
|
||||
function ClerkAuthLayout() {
|
||||
return (
|
||||
<div className='relative container grid h-svh flex-col items-center justify-center lg:max-w-none lg:grid-cols-2 lg:px-0'>
|
||||
<div className='bg-muted relative hidden h-full flex-col p-10 text-white lg:flex dark:border-r'>
|
||||
<div className='absolute inset-0 bg-slate-500' />
|
||||
<Link
|
||||
to='/'
|
||||
className='relative z-20 flex items-center text-lg font-medium'
|
||||
>
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 24 24'
|
||||
fill='none'
|
||||
stroke='currentColor'
|
||||
strokeWidth='2'
|
||||
strokeLinecap='round'
|
||||
strokeLinejoin='round'
|
||||
className='mr-2 h-6 w-6'
|
||||
>
|
||||
<path d='M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3' />
|
||||
</svg>
|
||||
Shadcn Admin
|
||||
</Link>
|
||||
|
||||
<ClerkFullLogo className='relative m-auto size-96' />
|
||||
|
||||
<div className='relative z-20 mt-auto'>
|
||||
<blockquote className='space-y-2'>
|
||||
<p className='text-lg'>
|
||||
“ Lorem ipsum dolor sit amet consectetur adipisicing elit.
|
||||
Sint, magni debitis inventore asperiores velit! ”
|
||||
</p>
|
||||
<footer className='text-sm'>John Doe</footer>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div className='lg:p-8'>
|
||||
<div className='relative mx-auto flex w-full flex-col items-center justify-center gap-4'>
|
||||
<LearnMore
|
||||
defaultOpen
|
||||
triggerProps={{
|
||||
className: 'absolute -top-12 right-0 sm:right-20 size-6',
|
||||
}}
|
||||
contentProps={{ side: 'top', align: 'end', className: 'w-auto' }}
|
||||
>
|
||||
Welcome to the example Clerk auth page. <br />
|
||||
Back to{' '}
|
||||
<Link
|
||||
to='/'
|
||||
className='underline decoration-dashed underline-offset-2'
|
||||
>
|
||||
Dashboard
|
||||
</Link>{' '}
|
||||
?
|
||||
</LearnMore>
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
14
src/routes/clerk/(auth)/sign-in.tsx
Normal file
14
src/routes/clerk/(auth)/sign-in.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { SignIn } from '@clerk/clerk-react'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
|
||||
export const Route = createFileRoute('/clerk/(auth)/sign-in')({
|
||||
component: () => (
|
||||
<SignIn
|
||||
initialValues={{
|
||||
emailAddress: 'your_mail+shadcn_admin@gmail.com',
|
||||
}}
|
||||
fallback={<Skeleton className='h-[30rem] w-[25rem]' />}
|
||||
/>
|
||||
),
|
||||
})
|
||||
9
src/routes/clerk/(auth)/sign-up.tsx
Normal file
9
src/routes/clerk/(auth)/sign-up.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { SignUp } from '@clerk/clerk-react'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
|
||||
export const Route = createFileRoute('/clerk/(auth)/sign-up')({
|
||||
component: () => (
|
||||
<SignUp fallback={<Skeleton className='h-[30rem] w-[25rem]' />} />
|
||||
),
|
||||
})
|
||||
6
src/routes/clerk/_authenticated/route.tsx
Normal file
6
src/routes/clerk/_authenticated/route.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { AuthenticatedLayout } from '@/components/layout/authenticated-layout'
|
||||
|
||||
export const Route = createFileRoute('/clerk/_authenticated')({
|
||||
component: AuthenticatedLayout,
|
||||
})
|
||||
184
src/routes/clerk/_authenticated/user-management.tsx
Normal file
184
src/routes/clerk/_authenticated/user-management.tsx
Normal file
@@ -0,0 +1,184 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
createFileRoute,
|
||||
Link,
|
||||
useNavigate,
|
||||
useRouter,
|
||||
} from '@tanstack/react-router'
|
||||
import { IconArrowUpRight, IconLoader2 } from '@tabler/icons-react'
|
||||
import { SignedIn, useAuth, UserButton } from '@clerk/clerk-react'
|
||||
import { ClerkLogo } from '@/assets/clerk-logo'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Header } from '@/components/layout/header'
|
||||
import { Main } from '@/components/layout/main'
|
||||
import { LearnMore } from '@/components/learn-more'
|
||||
import { Search } from '@/components/search'
|
||||
import { ThemeSwitch } from '@/components/theme-switch'
|
||||
import { columns } from '@/features/users/components/users-columns'
|
||||
import { UsersDialogs } from '@/features/users/components/users-dialogs'
|
||||
import { UsersPrimaryButtons } from '@/features/users/components/users-primary-buttons'
|
||||
import { UsersTable } from '@/features/users/components/users-table'
|
||||
import UsersProvider from '@/features/users/context/users-context'
|
||||
import { userListSchema } from '@/features/users/data/schema'
|
||||
import { users } from '@/features/users/data/users'
|
||||
|
||||
export const Route = createFileRoute('/clerk/_authenticated/user-management')({
|
||||
component: UserManagement,
|
||||
})
|
||||
|
||||
function UserManagement() {
|
||||
const [opened, setOpened] = useState(true)
|
||||
const { isLoaded, isSignedIn } = useAuth()
|
||||
|
||||
if (!isLoaded) {
|
||||
return (
|
||||
<div className='flex h-svh items-center justify-center'>
|
||||
<IconLoader2 className='size-8 animate-spin' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!isSignedIn) {
|
||||
return <Unauthorized />
|
||||
}
|
||||
|
||||
// Parse user list
|
||||
const userList = userListSchema.parse(users)
|
||||
return (
|
||||
<>
|
||||
<SignedIn>
|
||||
<UsersProvider>
|
||||
<Header fixed>
|
||||
<Search />
|
||||
<div className='ml-auto flex items-center space-x-4'>
|
||||
<ThemeSwitch />
|
||||
<UserButton />
|
||||
</div>
|
||||
</Header>
|
||||
|
||||
<Main>
|
||||
<div className='mb-2 flex flex-wrap items-center justify-between space-y-2'>
|
||||
<div>
|
||||
<h2 className='text-2xl font-bold tracking-tight'>User List</h2>
|
||||
<div className='flex gap-1'>
|
||||
<p className='text-muted-foreground'>
|
||||
Manage your users and their roles here.
|
||||
</p>
|
||||
<LearnMore
|
||||
open={opened}
|
||||
onOpenChange={setOpened}
|
||||
contentProps={{ side: 'right' }}
|
||||
>
|
||||
<p>
|
||||
This is the same as{' '}
|
||||
<Link
|
||||
to='/users'
|
||||
className='text-blue-500 underline decoration-dashed underline-offset-2'
|
||||
>
|
||||
'/users'
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<p className='mt-4'>
|
||||
You can sign out or manage/delete your account via the
|
||||
User Profile menu in the top-right corner of the page.
|
||||
<IconArrowUpRight className='inline-block size-4' />
|
||||
</p>
|
||||
</LearnMore>
|
||||
</div>
|
||||
</div>
|
||||
<UsersPrimaryButtons />
|
||||
</div>
|
||||
<div className='-mx-4 flex-1 overflow-auto px-4 py-1 lg:flex-row lg:space-y-0 lg:space-x-12'>
|
||||
<UsersTable data={userList} columns={columns} />
|
||||
</div>
|
||||
</Main>
|
||||
|
||||
<UsersDialogs />
|
||||
</UsersProvider>
|
||||
</SignedIn>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const COUNTDOWN = 5 // Countdown second
|
||||
|
||||
function Unauthorized() {
|
||||
const navigate = useNavigate()
|
||||
const { history } = useRouter()
|
||||
|
||||
const [opened, setOpened] = useState(true)
|
||||
const [cancelled, setCancelled] = useState(false)
|
||||
const [countdown, setCountdown] = useState(COUNTDOWN)
|
||||
|
||||
// Set and run the countdown conditionally
|
||||
useEffect(() => {
|
||||
if (cancelled || opened) return
|
||||
const interval = setInterval(() => {
|
||||
setCountdown((prev) => (prev > 0 ? prev - 1 : 0))
|
||||
}, 1000)
|
||||
return () => clearInterval(interval)
|
||||
}, [cancelled, opened])
|
||||
|
||||
// Navigate to sign-in page when countdown hits 0
|
||||
useEffect(() => {
|
||||
if (countdown > 0) return
|
||||
navigate({ to: '/clerk/sign-in' })
|
||||
}, [countdown, navigate])
|
||||
|
||||
return (
|
||||
<div className='h-svh'>
|
||||
<div className='m-auto flex h-full w-full flex-col items-center justify-center gap-2'>
|
||||
<h1 className='text-[7rem] leading-tight font-bold'>401</h1>
|
||||
<span className='font-medium'>Unauthorized Access</span>
|
||||
<p className='text-muted-foreground text-center'>
|
||||
You must be authenticated via Clerk{' '}
|
||||
<sup>
|
||||
<LearnMore open={opened} onOpenChange={setOpened}>
|
||||
<p>
|
||||
This is the same as{' '}
|
||||
<Link
|
||||
to='/users'
|
||||
className='text-blue-500 underline decoration-dashed underline-offset-2'
|
||||
>
|
||||
'/users'
|
||||
</Link>
|
||||
.{' '}
|
||||
</p>
|
||||
<p>You must first sign in using Clerk to access this route. </p>
|
||||
|
||||
<p className='mt-4'>
|
||||
After signing in, you'll be able to sign out or delete your
|
||||
account via the User Profile dropdown on this page.
|
||||
</p>
|
||||
</LearnMore>
|
||||
</sup>
|
||||
<br />
|
||||
to access this resource.
|
||||
</p>
|
||||
<div className='mt-6 flex gap-4'>
|
||||
<Button variant='outline' onClick={() => history.go(-1)}>
|
||||
Go Back
|
||||
</Button>
|
||||
<Button onClick={() => navigate({ to: '/clerk/sign-in' })}>
|
||||
<ClerkLogo className='invert' /> Sign in
|
||||
</Button>
|
||||
</div>
|
||||
<div className='mt-4 h-8 text-center'>
|
||||
{!cancelled && !opened && (
|
||||
<>
|
||||
<p>
|
||||
{countdown > 0
|
||||
? `Redirecting to Sign In page in ${countdown}s`
|
||||
: `Redirecting...`}
|
||||
</p>
|
||||
<Button variant='link' onClick={() => setCancelled(true)}>
|
||||
Cancel Redirect
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
130
src/routes/clerk/route.tsx
Normal file
130
src/routes/clerk/route.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import { createFileRoute, Outlet } from '@tanstack/react-router'
|
||||
import { IconExternalLink, IconKeyOff } from '@tabler/icons-react'
|
||||
import { ClerkProvider } from '@clerk/clerk-react'
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { SidebarTrigger } from '@/components/ui/sidebar'
|
||||
import { AuthenticatedLayout } from '@/components/layout/authenticated-layout'
|
||||
import { Main } from '@/components/layout/main'
|
||||
import { ThemeSwitch } from '@/components/theme-switch'
|
||||
|
||||
export const Route = createFileRoute('/clerk')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
// Import your Publishable Key
|
||||
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
|
||||
|
||||
function RouteComponent() {
|
||||
if (!PUBLISHABLE_KEY) {
|
||||
return <MissingClerkPubKey />
|
||||
}
|
||||
|
||||
return (
|
||||
<ClerkProvider
|
||||
publishableKey={PUBLISHABLE_KEY}
|
||||
afterSignOutUrl='/clerk/sign-in'
|
||||
signInUrl='/clerk/sign-in'
|
||||
signUpUrl='/clerk/sign-up'
|
||||
signInFallbackRedirectUrl='/clerk/user-management'
|
||||
signUpFallbackRedirectUrl='/clerk/user-management'
|
||||
>
|
||||
<Outlet />
|
||||
</ClerkProvider>
|
||||
)
|
||||
}
|
||||
|
||||
function MissingClerkPubKey() {
|
||||
const codeBlock =
|
||||
'bg-foreground/10 rounded-sm py-0.5 px-1 text-xs text-foreground font-bold'
|
||||
return (
|
||||
<AuthenticatedLayout>
|
||||
<div className='bg-background flex h-16 justify-between p-4'>
|
||||
<SidebarTrigger variant='outline' className='scale-125 sm:scale-100' />
|
||||
<ThemeSwitch />
|
||||
</div>
|
||||
<Main className='flex flex-col items-center justify-start'>
|
||||
<div className='max-w-2xl'>
|
||||
<Alert>
|
||||
<IconKeyOff className='size-4' />
|
||||
<AlertTitle>No Publishable Key Found!</AlertTitle>
|
||||
<AlertDescription>
|
||||
<p className='text-balance'>
|
||||
You need to generate a publishable key from Clerk and put it
|
||||
inside the <code className={codeBlock}>.env</code> file.
|
||||
</p>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<h1 className='mt-4 text-2xl font-bold'>Set your Clerk API key</h1>
|
||||
<div className='text-foreground/75 mt-4 flex flex-col gap-y-4'>
|
||||
<ol className='list-inside list-decimal space-y-1.5'>
|
||||
<li>
|
||||
In the{' '}
|
||||
<a
|
||||
href='https://go.clerk.com/GttUAaK'
|
||||
target='_blank'
|
||||
className='underline decoration-dashed underline-offset-4 hover:decoration-solid'
|
||||
>
|
||||
Clerk
|
||||
<sup>
|
||||
<IconExternalLink className='inline-block size-4' />
|
||||
</sup>
|
||||
</a>{' '}
|
||||
Dashboard, navigate to the API keys page.
|
||||
</li>
|
||||
<li>
|
||||
In the <strong>Quick Copy</strong> section, copy your Clerk
|
||||
Publishable Key.
|
||||
</li>
|
||||
<li>
|
||||
Rename <code className={codeBlock}>.env.example</code> to{' '}
|
||||
<code className={codeBlock}>.env</code>
|
||||
</li>
|
||||
<li>
|
||||
Paste your key into your <code className={codeBlock}>.env</code>{' '}
|
||||
file.
|
||||
</li>
|
||||
</ol>
|
||||
<p>The final result should resemble the following:</p>
|
||||
|
||||
<div className='@container space-y-2 rounded-md bg-slate-800 px-3 py-3 text-sm text-slate-200'>
|
||||
<span className='pl-1'>.env</span>
|
||||
<pre className='overflow-auto overscroll-x-contain rounded bg-slate-950 px-2 py-1 text-xs'>
|
||||
<code>
|
||||
<span className='before:text-slate-400 md:before:pr-2 md:before:content-["1."]'>
|
||||
VITE_CLERK_PUBLISHABLE_KEY=YOUR_PUBLISHABLE_KEY
|
||||
</span>
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator className='my-4 w-full' />
|
||||
|
||||
<Alert>
|
||||
<AlertTitle>Clerk Integration is Optional</AlertTitle>
|
||||
<AlertDescription>
|
||||
<p className='text-balance'>
|
||||
The Clerk integration lives entirely inside{' '}
|
||||
<code className={codeBlock}>src/routes/clerk</code>. If you plan
|
||||
to use Clerk as your auth service, you might want to place{' '}
|
||||
<code className={codeBlock}>ClerkProvider</code> at the root
|
||||
route.
|
||||
</p>
|
||||
<p>
|
||||
However, if you don't plan to use Clerk, you can safely remove
|
||||
this directory and related dependency_{' '}
|
||||
<code className={codeBlock}>@clerk/clerk-react</code>.
|
||||
</p>
|
||||
<p className='mt-2 text-sm'>
|
||||
This setup is modular by design and won't affect the rest of the
|
||||
application.
|
||||
</p>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
</Main>
|
||||
</AuthenticatedLayout>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user