Getting started on Tech Tracker. Added TV Context
This commit is contained in:
@ -17,7 +17,7 @@
|
|||||||
"typecheck": "tsc --noEmit"
|
"typecheck": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^5.1.0",
|
"@hookform/resolvers": "^5.1.1",
|
||||||
"@radix-ui/react-avatar": "^1.1.10",
|
"@radix-ui/react-avatar": "^1.1.10",
|
||||||
"@radix-ui/react-checkbox": "^1.3.2",
|
"@radix-ui/react-checkbox": "^1.3.2",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||||
|
38
pnpm-lock.yaml
generated
38
pnpm-lock.yaml
generated
@ -9,8 +9,8 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^5.1.0
|
specifier: ^5.1.1
|
||||||
version: 5.1.0(react-hook-form@7.57.0(react@19.1.0))
|
version: 5.1.1(react-hook-form@7.57.0(react@19.1.0))
|
||||||
'@radix-ui/react-avatar':
|
'@radix-ui/react-avatar':
|
||||||
specifier: ^1.1.10
|
specifier: ^1.1.10
|
||||||
version: 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 1.1.10(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
@ -274,8 +274,8 @@ packages:
|
|||||||
'@floating-ui/utils@0.2.9':
|
'@floating-ui/utils@0.2.9':
|
||||||
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
||||||
|
|
||||||
'@hookform/resolvers@5.1.0':
|
'@hookform/resolvers@5.1.1':
|
||||||
resolution: {integrity: sha512-A5tY8gxqvvR0lFfropqpy/gUDOxjwT7LZCxJ8lNA9puK7nFNRl/O0egGKxzdF4JSz/pnnT1O8g76P/YMyTBEFw==}
|
resolution: {integrity: sha512-J/NVING3LMAEvexJkyTLjruSm7aOFx7QX21pzkiJfMoNG0wl5aFEjLTl7ay7IQb9EWY6AkrBy7tHL2Alijpdcg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react-hook-form: ^7.55.0
|
react-hook-form: ^7.55.0
|
||||||
|
|
||||||
@ -1723,8 +1723,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
|
|
||||||
acorn@8.14.1:
|
acorn@8.15.0:
|
||||||
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
|
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@ -3697,7 +3697,7 @@ snapshots:
|
|||||||
|
|
||||||
'@floating-ui/utils@0.2.9': {}
|
'@floating-ui/utils@0.2.9': {}
|
||||||
|
|
||||||
'@hookform/resolvers@5.1.0(react-hook-form@7.57.0(react@19.1.0))':
|
'@hookform/resolvers@5.1.1(react-hook-form@7.57.0(react@19.1.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@standard-schema/utils': 0.3.0
|
'@standard-schema/utils': 0.3.0
|
||||||
react-hook-form: 7.57.0(react@19.1.0)
|
react-hook-form: 7.57.0(react@19.1.0)
|
||||||
@ -5157,15 +5157,15 @@ snapshots:
|
|||||||
|
|
||||||
'@xtuc/long@4.2.2': {}
|
'@xtuc/long@4.2.2': {}
|
||||||
|
|
||||||
acorn-import-attributes@1.9.5(acorn@8.14.1):
|
acorn-import-attributes@1.9.5(acorn@8.15.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.14.1
|
acorn: 8.15.0
|
||||||
|
|
||||||
acorn-jsx@5.3.2(acorn@8.14.1):
|
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.14.1
|
acorn: 8.15.0
|
||||||
|
|
||||||
acorn@8.14.1: {}
|
acorn@8.15.0: {}
|
||||||
|
|
||||||
agent-base@6.0.2:
|
agent-base@6.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -5789,8 +5789,8 @@ snapshots:
|
|||||||
|
|
||||||
espree@10.3.0:
|
espree@10.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.14.1
|
acorn: 8.15.0
|
||||||
acorn-jsx: 5.3.2(acorn@8.14.1)
|
acorn-jsx: 5.3.2(acorn@8.15.0)
|
||||||
eslint-visitor-keys: 4.2.0
|
eslint-visitor-keys: 4.2.0
|
||||||
|
|
||||||
esquery@1.6.0:
|
esquery@1.6.0:
|
||||||
@ -5997,8 +5997,8 @@ snapshots:
|
|||||||
|
|
||||||
import-in-the-middle@1.14.0:
|
import-in-the-middle@1.14.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.14.1
|
acorn: 8.15.0
|
||||||
acorn-import-attributes: 1.9.5(acorn@8.14.1)
|
acorn-import-attributes: 1.9.5(acorn@8.15.0)
|
||||||
cjs-module-lexer: 1.4.3
|
cjs-module-lexer: 1.4.3
|
||||||
module-details-from-path: 1.0.4
|
module-details-from-path: 1.0.4
|
||||||
|
|
||||||
@ -6908,7 +6908,7 @@ snapshots:
|
|||||||
terser@5.41.0:
|
terser@5.41.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/source-map': 0.3.6
|
'@jridgewell/source-map': 0.3.6
|
||||||
acorn: 8.14.1
|
acorn: 8.15.0
|
||||||
commander: 2.20.3
|
commander: 2.20.3
|
||||||
source-map-support: 0.5.21
|
source-map-support: 0.5.21
|
||||||
|
|
||||||
@ -7000,7 +7000,7 @@ snapshots:
|
|||||||
|
|
||||||
unplugin@1.0.1:
|
unplugin@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
acorn: 8.14.1
|
acorn: 8.15.0
|
||||||
chokidar: 3.6.0
|
chokidar: 3.6.0
|
||||||
webpack-sources: 3.3.2
|
webpack-sources: 3.3.2
|
||||||
webpack-virtual-modules: 0.5.0
|
webpack-virtual-modules: 0.5.0
|
||||||
@ -7077,7 +7077,7 @@ snapshots:
|
|||||||
'@webassemblyjs/ast': 1.14.1
|
'@webassemblyjs/ast': 1.14.1
|
||||||
'@webassemblyjs/wasm-edit': 1.14.1
|
'@webassemblyjs/wasm-edit': 1.14.1
|
||||||
'@webassemblyjs/wasm-parser': 1.14.1
|
'@webassemblyjs/wasm-parser': 1.14.1
|
||||||
acorn: 8.14.1
|
acorn: 8.15.0
|
||||||
browserslist: 4.25.0
|
browserslist: 4.25.0
|
||||||
chrome-trace-event: 1.0.4
|
chrome-trace-event: 1.0.4
|
||||||
enhanced-resolve: 5.18.1
|
enhanced-resolve: 5.18.1
|
||||||
|
63
public/icons/tv/enter.svg
Normal file
63
public/icons/tv/enter.svg
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 12.7 12.7"
|
||||||
|
version="1.1"
|
||||||
|
id="svg513"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="Fullscreen.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview515"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="8.4359982"
|
||||||
|
inkscape:cx="69.108597"
|
||||||
|
inkscape:cy="37.458519"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="3832"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs510" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:#b3b3b3;stroke-width:0.193912"
|
||||||
|
d="M 5.8615386,0 H 0 V 5.8615386 H 0.97692256 V 0.97692327 H 5.8615386 Z"
|
||||||
|
id="path396-6"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#b3b3b3;stroke-width:0.193912"
|
||||||
|
d="M 0,6.8384619 V 12.7 H 5.8615386 V 11.723076 H 0.97692256 V 6.8384619 Z"
|
||||||
|
id="path396-52"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#b3b3b3;stroke-width:0.193912"
|
||||||
|
d="M 6.8384613,12.7 H 12.7 V 6.8384619 H 11.723078 V 11.723076 H 6.8384613 Z"
|
||||||
|
id="path396-4"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#b3b3b3;stroke-width:0.193912"
|
||||||
|
d="M 12.7,5.8615386 V 0 H 6.8384613 V 0.97692327 H 11.723078 V 5.8615386 Z"
|
||||||
|
id="path396-0"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
63
public/icons/tv/exit.svg
Normal file
63
public/icons/tv/exit.svg
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="48"
|
||||||
|
height="48"
|
||||||
|
viewBox="0 0 12.7 12.7"
|
||||||
|
version="1.1"
|
||||||
|
id="svg513"
|
||||||
|
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
|
||||||
|
sodipodi:docname="ExitFullscreen.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview515"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="8.4359982"
|
||||||
|
inkscape:cx="69.108597"
|
||||||
|
inkscape:cy="37.458519"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1017"
|
||||||
|
inkscape:window-x="3832"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs510" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1">
|
||||||
|
<path
|
||||||
|
style="fill:#b3b3b3;stroke-width:0.193906"
|
||||||
|
d="M 0,5.8620045 H 5.8615381 L 5.8621526,0 H 4.8849607 L 4.8846152,4.8851478 H 0 Z"
|
||||||
|
id="path190"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#b3b3b3;stroke-width:0.193906"
|
||||||
|
d="M 6.8384615,8.6556325e-4 V 5.8620045 H 12.7 V 4.8851478 H 7.815384 V 8.6556325e-4 Z"
|
||||||
|
id="path396"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#b3b3b3;stroke-width:0.193906"
|
||||||
|
d="M 12.7,6.8388612 H 6.8384615 V 12.7 H 7.815384 V 7.8157173 H 12.7 Z"
|
||||||
|
id="path396-5"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#b3b3b3;stroke-width:0.193906"
|
||||||
|
d="M 5.8615381,12.7 V 6.8388612 H 0 V 7.8157173 H 4.8846152 V 12.7 Z"
|
||||||
|
id="path396-1"
|
||||||
|
sodipodi:nodetypes="ccccccc" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -35,7 +35,7 @@ export const GET = async (request: NextRequest) => {
|
|||||||
if (type === 'invite') return redirect('/sign-up');
|
if (type === 'invite') return redirect('/sign-up');
|
||||||
}
|
}
|
||||||
return redirect(
|
return redirect(
|
||||||
`/?error=${encodeURIComponent(error?.message || 'Unknown error')}`,
|
`/?error=${encodeURIComponent(error?.message ?? 'Unknown error')}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useAuth } from '@/components/context/auth';
|
import { useAuth } from '@/components/context';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
|
|
||||||
const AuthSuccessPage = () => {
|
const AuthSuccessPage = () => {
|
||||||
const { refreshUserData, isAuthenticated } = useAuth();
|
const { refreshUserData } = useAuth();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -19,7 +19,7 @@ import {
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { forgotPassword } from '@/lib/actions';
|
import { forgotPassword } from '@/lib/actions';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useAuth } from '@/components/context/auth';
|
import { useAuth } from '@/components/context';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { StatusMessage, SubmitButton } from '@/components/default';
|
import { StatusMessage, SubmitButton } from '@/components/default';
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useAuth } from '@/components/context/auth';
|
import { useAuth } from '@/components/context';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
|
@ -20,7 +20,7 @@ import {
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { signIn } from '@/lib/actions';
|
import { signIn } from '@/lib/actions';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useAuth } from '@/components/context/auth';
|
import { useAuth } from '@/components/context';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { StatusMessage, SubmitButton } from '@/components/default';
|
import { StatusMessage, SubmitButton } from '@/components/default';
|
||||||
import { Separator } from '@/components/ui';
|
import { Separator } from '@/components/ui';
|
||||||
|
@ -7,7 +7,7 @@ import Link from 'next/link';
|
|||||||
import { signUp } from '@/lib/actions';
|
import { signUp } from '@/lib/actions';
|
||||||
import { StatusMessage, SubmitButton } from '@/components/default';
|
import { StatusMessage, SubmitButton } from '@/components/default';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useAuth } from '@/components/context/auth';
|
import { useAuth } from '@/components/context';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@ -26,7 +26,7 @@ import {
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
SignInWithApple,
|
SignInWithApple,
|
||||||
SignInWithMicrosoft
|
SignInWithMicrosoft,
|
||||||
} from '@/components/default/auth';
|
} from '@/components/default/auth';
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
@ -201,8 +201,8 @@ const SignUp = () => {
|
|||||||
<span className='text-sm text-muted-foreground'>or</span>
|
<span className='text-sm text-muted-foreground'>or</span>
|
||||||
<Separator className='flex-1 bg-accent py-0.5' />
|
<Separator className='flex-1 bg-accent py-0.5' />
|
||||||
</div>
|
</div>
|
||||||
<SignInWithMicrosoft type='signUp' />
|
<SignInWithMicrosoft />
|
||||||
<SignInWithApple type='signUp' />
|
<SignInWithApple />
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
import '@/styles/globals.css';
|
import '@/styles/globals.css';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { ThemeProvider } from '@/components/context/theme';
|
import { AuthProvider, ThemeProvider } from '@/components/context';
|
||||||
import { AuthProvider } from '@/components/context/auth';
|
|
||||||
import Navigation from '@/components/default/navigation';
|
import Navigation from '@/components/default/navigation';
|
||||||
import Footer from '@/components/default/footer';
|
import Footer from '@/components/default/footer';
|
||||||
import { Button, Toaster } from '@/components/ui';
|
import { Button, Toaster } from '@/components/ui';
|
||||||
@ -60,21 +59,6 @@ const GlobalError = ({ error, reset = undefined }: GlobalErrorProps) => {
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
|
||||||
<html lang='en'>
|
|
||||||
<body>
|
|
||||||
{/* `NextError` is the default Next.js error page component. Its type
|
|
||||||
definition requires a `statusCode` prop. However, since the App Router
|
|
||||||
does not expose status codes for errors, we simply pass 0 to render a
|
|
||||||
generic error message. */}
|
|
||||||
<NextError statusCode={0} />
|
|
||||||
{reset !== undefined && (
|
|
||||||
<Button onClick={() => reset()}>Try again</Button>
|
|
||||||
)}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GlobalError;
|
export default GlobalError;
|
||||||
|
@ -2,8 +2,11 @@ import type { Metadata } from 'next';
|
|||||||
import '@/styles/globals.css';
|
import '@/styles/globals.css';
|
||||||
import { Geist } from 'next/font/google';
|
import { Geist } from 'next/font/google';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { ThemeProvider } from '@/components/context/theme';
|
import {
|
||||||
import { AuthProvider } from '@/components/context/auth';
|
AuthProvider,
|
||||||
|
ThemeProvider,
|
||||||
|
TVModeProvider,
|
||||||
|
} from '@/components/context';
|
||||||
import Navigation from '@/components/default/navigation';
|
import Navigation from '@/components/default/navigation';
|
||||||
import Footer from '@/components/default/footer';
|
import Footer from '@/components/default/footer';
|
||||||
import { Toaster } from '@/components/ui';
|
import { Toaster } from '@/components/ui';
|
||||||
@ -357,19 +360,21 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
|
|||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<main className='min-h-screen flex flex-col items-center'>
|
<TVModeProvider>
|
||||||
<div className='flex-1 w-full flex flex-col gap-20 items-center'>
|
<main className='min-h-screen flex flex-col items-center'>
|
||||||
<Navigation />
|
<div className='flex-1 w-full flex flex-col gap-20 items-center'>
|
||||||
<div
|
<Navigation />
|
||||||
className='flex flex-col gap-20 max-w-5xl
|
<div
|
||||||
p-5 w-full items-center'
|
className='flex flex-col gap-20 max-w-5xl
|
||||||
>
|
p-5 w-full items-center'
|
||||||
{children}
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<Footer />
|
||||||
<Footer />
|
</main>
|
||||||
</main>
|
<Toaster />
|
||||||
<Toaster />
|
</TVModeProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
SignInSignUp,
|
SignInSignUp,
|
||||||
SignInWithApple,
|
SignInWithApple,
|
||||||
SignInWithMicrosoft
|
SignInWithMicrosoft,
|
||||||
} from '@/components/default/auth';
|
} from '@/components/default/auth';
|
||||||
|
|
||||||
const HomePage = async () => {
|
const HomePage = async () => {
|
||||||
|
@ -110,7 +110,7 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
data: { subscription },
|
data: { subscription },
|
||||||
} = supabase.auth.onAuthStateChange(async (event, session) => {
|
} = supabase.auth.onAuthStateChange(async (event, _session) => {
|
||||||
console.log('Auth state change:', event); // Debug log
|
console.log('Auth state change:', event); // Debug log
|
||||||
|
|
||||||
if (event === 'SIGNED_IN') {
|
if (event === 'SIGNED_IN') {
|
79
src/components/context/TVMode.tsx
Normal file
79
src/components/context/TVMode.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
'use client';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import React, { createContext, useContext, useState } from 'react';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import { useAuth } from '@/components/context';
|
||||||
|
import { Button, type buttonVariants } from '@/components/ui';
|
||||||
|
import { type ComponentProps } from 'react';
|
||||||
|
import { type VariantProps } from 'class-variance-authority';
|
||||||
|
|
||||||
|
type TVModeContextProps = {
|
||||||
|
tvMode: boolean;
|
||||||
|
toggleTVMode: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TVToggleProps = {
|
||||||
|
className?: ComponentProps<'button'>['className'];
|
||||||
|
buttonSize?: VariantProps<typeof buttonVariants>['size'];
|
||||||
|
buttonVariant?: VariantProps<typeof buttonVariants>['variant'];
|
||||||
|
imageWidth?: number;
|
||||||
|
imageHeight?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TVModeContext = createContext<TVModeContextProps | undefined>(undefined);
|
||||||
|
|
||||||
|
export const TVModeProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const [tvMode, setTVMode] = useState(false);
|
||||||
|
const toggleTVMode = () => {
|
||||||
|
setTVMode((prev) => !prev);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<TVModeContext.Provider value={{ tvMode, toggleTVMode }}>
|
||||||
|
{children}
|
||||||
|
</TVModeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useTVMode = () => {
|
||||||
|
const context = useContext(TVModeContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useTVMode must be used within a TVModeProvider');
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TVToggle = ({
|
||||||
|
className = 'my-auto cursor-pointer',
|
||||||
|
buttonSize = 'default',
|
||||||
|
buttonVariant = 'link',
|
||||||
|
imageWidth = 25,
|
||||||
|
imageHeight = 25,
|
||||||
|
}: TVToggleProps) => {
|
||||||
|
const { tvMode, toggleTVMode } = useTVMode();
|
||||||
|
const { isAuthenticated } = useAuth();
|
||||||
|
if (!isAuthenticated) return <div />;
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={toggleTVMode}
|
||||||
|
className={className}
|
||||||
|
size={buttonSize}
|
||||||
|
variant={buttonVariant}
|
||||||
|
>
|
||||||
|
{tvMode ? (
|
||||||
|
<Image
|
||||||
|
src='/icons/tv/exit.svg'
|
||||||
|
alt='Exit TV Mode'
|
||||||
|
width={imageWidth}
|
||||||
|
height={imageHeight}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Image
|
||||||
|
src='/icons/tv/enter.svg'
|
||||||
|
alt='Exit TV Mode'
|
||||||
|
width={imageWidth}
|
||||||
|
height={imageHeight}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
@ -20,10 +20,9 @@ export const ThemeProvider = ({
|
|||||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ThemeToggleProps
|
type ThemeToggleProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
||||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
||||||
size?: number;
|
size?: number;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ThemeToggle = ({ size = 1, ...props }: ThemeToggleProps) => {
|
export const ThemeToggle = ({ size = 1, ...props }: ThemeToggleProps) => {
|
||||||
const { setTheme, resolvedTheme } = useTheme();
|
const { setTheme, resolvedTheme } = useTheme();
|
3
src/components/context/index.tsx
Normal file
3
src/components/context/index.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { AuthProvider, useAuth } from './Auth';
|
||||||
|
export { ThemeProvider, ThemeToggle } from './Theme';
|
||||||
|
export { TVModeProvider, useTVMode, TVToggle } from './TVMode';
|
@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { signInWithApple } from '@/lib/actions';
|
import { signInWithApple } from '@/lib/actions';
|
||||||
import { StatusMessage, SubmitButton } from '@/components/default';
|
import { StatusMessage, SubmitButton } from '@/components/default';
|
||||||
import { useAuth } from '@/components/context/auth';
|
import { useAuth } from '@/components/context';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
@ -37,7 +37,7 @@ export const SignInWithApple = ({
|
|||||||
// Redirect to Apple OAuth page
|
// Redirect to Apple OAuth page
|
||||||
window.location.href = result.data;
|
window.location.href = result.data;
|
||||||
} else {
|
} else {
|
||||||
setStatusMessage(`Error: ${result.error}`);
|
setStatusMessage(`Error signing in with Apple!`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setStatusMessage(
|
setStatusMessage(
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { signInWithMicrosoft } from '@/lib/actions';
|
import { signInWithMicrosoft } from '@/lib/actions';
|
||||||
import { StatusMessage, SubmitButton } from '@/components/default';
|
import { StatusMessage, SubmitButton } from '@/components/default';
|
||||||
import { useAuth } from '@/components/context/auth';
|
import { useAuth } from '@/components/context';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { type buttonVariants } from '@/components/ui';
|
import { type buttonVariants } from '@/components/ui';
|
||||||
@ -35,7 +35,7 @@ export const SignInWithMicrosoft = ({
|
|||||||
// Redirect to Microsoft OAuth page
|
// Redirect to Microsoft OAuth page
|
||||||
window.location.href = result.data;
|
window.location.href = result.data;
|
||||||
} else {
|
} else {
|
||||||
setStatusMessage(`Error: ${result.error}`);
|
setStatusMessage(`Error: Could not sign in with Microsoft!`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setStatusMessage(
|
setStatusMessage(
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui';
|
} from '@/components/ui';
|
||||||
import { useAuth } from '@/components/context/auth';
|
import { useAuth } from '@/components/context';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { signOut } from '@/lib/actions';
|
import { signOut } from '@/lib/actions';
|
||||||
import { User } from 'lucide-react';
|
import { User } from 'lucide-react';
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Button } from '@/components/ui';
|
import { Button } from '@/components/ui';
|
||||||
import NavigationAuth from './auth';
|
import NavigationAuth from './auth';
|
||||||
import { ThemeToggle } from '@/components/context/theme';
|
import { ThemeToggle, TVToggle } from '@/components/context';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
|
||||||
const Navigation = () => {
|
const Navigation = () => {
|
||||||
@ -30,6 +30,7 @@ const Navigation = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
|
<TVToggle />
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
<NavigationAuth />
|
<NavigationAuth />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useFileUpload } from '@/lib/hooks/useFileUpload';
|
import { useFileUpload } from '@/lib/hooks/useFileUpload';
|
||||||
import { useAuth } from '@/components/context/auth';
|
import { useAuth } from '@/components/context';
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
AvatarFallback,
|
AvatarFallback,
|
||||||
|
@ -13,7 +13,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
} from '@/components/ui';
|
} from '@/components/ui';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useAuth } from '@/components/context/auth';
|
import { useAuth } from '@/components/context';
|
||||||
import { SubmitButton } from '@/components/default';
|
import { SubmitButton } from '@/components/default';
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
|
@ -122,18 +122,15 @@ export const ResetPasswordForm = ({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{statusMessage && (
|
{statusMessage &&
|
||||||
<div
|
(statusMessage.includes('Error') ||
|
||||||
className={`text-sm text-center ${
|
statusMessage.includes('error') ||
|
||||||
statusMessage.includes('Error') ||
|
statusMessage.includes('failed') ||
|
||||||
statusMessage.includes('failed')
|
statusMessage.includes('invalid') ? (
|
||||||
? 'text-destructive'
|
<StatusMessage message={{ error: statusMessage }} />
|
||||||
: 'text-green-600'
|
) : (
|
||||||
}`}
|
<StatusMessage message={{ message: statusMessage }} />
|
||||||
>
|
))}
|
||||||
{statusMessage}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className='flex justify-center'>
|
<div className='flex justify-center'>
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { CardHeader } from '@/components/ui';
|
import { CardHeader } from '@/components/ui';
|
||||||
import { SubmitButton } from '@/components/default';
|
import { SubmitButton } from '@/components/default';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useAuth } from '@/components/context/auth';
|
import { useAuth } from '@/components/context';
|
||||||
import { signOut } from '@/lib/actions';
|
import { signOut } from '@/lib/actions';
|
||||||
|
|
||||||
export const SignOut = () => {
|
export const SignOut = () => {
|
||||||
|
@ -149,7 +149,7 @@ export const getUser = async (): Promise<Result<User>> => {
|
|||||||
const { data, error } = await supabase.auth.getUser();
|
const { data, error } = await supabase.auth.getUser();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
return { success: true, data: data.user };
|
return { success: true, data: data.user };
|
||||||
} catch (error) {
|
} catch {
|
||||||
return { success: false, error: 'Could not get user!' };
|
return { success: false, error: `Could not get user!` };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
export * from './auth';
|
export * from './auth';
|
||||||
export * from './public';
|
export * from './public';
|
||||||
//export * from './resizeImage';
|
|
||||||
export * from './storage';
|
export * from './storage';
|
||||||
export * from './useFileUpload';
|
export * from './useFileUpload';
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { useState, useRef } from 'react';
|
import { useState, useRef } from 'react';
|
||||||
import { replaceFile, uploadFile } from '@/lib/hooks';
|
import { replaceFile, uploadFile } from '@/lib/hooks';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useAuth } from '@/components/context/auth';
|
import { useAuth } from '@/components/context';
|
||||||
import { resizeImage } from '@/lib/hooks';
|
import { resizeImage } from '@/lib/hooks';
|
||||||
import type { Result } from '.';
|
import type { Result } from '.';
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user