backend/src/handler/user.rs
backend/src/handler/user/validate_name.rs → backend/src/handler/user/has_user.rs
loading...
frontend/app/(main)/[owner]/[repo]/[...filePath]/ui/file-body.tsx
frontend/app/(main)/[owner]/[repo]/questions/[number]/ui/answer-dropdown.tsx
frontend/app/(main)/[owner]/[repo]/questions/[number]/ui/comment-input.tsx
frontend/app/(main)/[owner]/[repo]/questions/[number]/ui/question-dropdown.tsx
frontend/app/(main)/[owner]/[repo]/ui/dialog/repo-file-dialog.tsx frontend/app/(main)/[owner]/[repo]/util/diff.ts frontend/app/(main)/ui/auth-blocker-dialog.tsx frontend/app/(main)/ui/main-header.tsx 1 " use client " ; 2 3 import { useRouter } from " next/navigation " ; 4 import { use , useCallback , useEffect , useMemo , useRef , useState } from " react " ; 5 import type { RepositoryPreviewEntry , RepositoryTreeEntry } from " @/lib/dto " ; 6 import { 17 const getStartingLine = ( hunk : DiffHunk ): number => { 18 const firstLine = hunk [ 0 ]; 19 return firstLine . lhs 20 ? firstLine . lhs . line_number 21 : firstLine . rhs ! . line_number ; 22 } ; 23 24 const sortedHunks = 1 " use client " ; 2 3 import { VisuallyHidden } from " @radix-ui/react-visually-hidden " ; 4 import { useState , useTransition } from " react " ; 5 import { type AuthActionResult , login , signup } from " @/actions " ; 6 import { Dialog , DialogContent , DialogTitle 8 DropdownMenuItem , 9 DropdownMenuTrigger , 10 } from " @/ui/dropdown-menu " ; 11 import Link from " @/ui/link " ; 12 import { cn } from " @/util " ; 13 import { User } from " lucide-react " ; 14 import { 1 " use server " ; 2 .. 3 import { redirect } from " next/navigation " ; 4 5 import { 6 authorizeDevice , 1 " use server " ; 2 3 import { refresh } from " next/cache 1 import type { Metadata } from " next " ; 2 import { IBM_Plex_Sans , Inconsolata , League_Spartan } from " next/font/google " ; 3 import " ./globals.css " ; 4 5 export const metadata : Metadata = { 6 title : " gitdot " , 1 import 18 type VoteRequest , 19 type VoteResponse , 20 VoteResponseSchema , 21 } from " ../dto " ; 22 import { 23 GITDOT_SERVER_URL , .. 25 authPatch , 26 authPost , 27 handleResponse , 28 } from " ./util " 14 handleResponse , 15 NotFound , 16 } from " ./util " ; 17 18 export async function validateUsername ( username : string ): Promise < boolean > { 19 const response = await authHead ( `${ GITDOT_SERVER_URL } /user/ ${ username }` ); 20 return response . ok ; 154 } 155 return emailTester . test ( email ); 156 } 157 158 export function validateName ( username : string ): boolean { 159 return !! username && username . length >= 2 ; 160 } 161 162 export function validatePassword ( password : string 1 mod get_current_user; 2 mod get_user; 3 mod list_user_repositories ; 4 mod validate_name ; 5 6 use chrono ::{ DateTime , Utc }; 7 use uuid :: Uuid ; 8 1 mod get_current_user; 2 mod get_user; 3 mod has_user ; 4 mod list_user_repositories ; 5 6 use chrono ::{ DateTime , Utc }; 7 use uuid :: Uuid ; 8
9 use crate :: model :: User ; 10 11 pub use get_current_user :: GetCurrentUserRequest ; 12 pub use get_user :: GetUserRequest ; 13 pub use list_user_repositories :: ListUserRepositoriesRequest ; 14 pub use validate_name :: ValidateNameRequest ; 15 16 #[derive( 1 use crate :: dto :: OwnerName ; 2 use crate :: error :: UserError ; 3 4 #[derive( Debug , Clone )] 5 pub struct ValidateNameRequest { 6 pub name : OwnerName , 7 } 8 9 impl ValidateNameRequest { 10 pub fn new ( name : & str ) -> Result < Self , UserError > { 11 Ok ( Self { 12 name : OwnerName :: try_new ( name ) 13 . map_err ( | e | UserError :: InvalidUserName ( e . to_string ()))?, 1 use crate :: dto :: OwnerName ; 2 use crate :: error :: UserError ; 3 4 #[derive( Debug , Clone )] 5 pub struct HasUserRequest { 6 pub name : OwnerName , 7 } 8 9 impl HasUserRequest { 10 pub fn new ( 1 use async_trait ::async_trait; 2 3 use crate :: dto ::{ 4 GetCurrentUserRequest , GetUserRequest , ListUserRepositoriesRequest , RepositoryResponse , 5 UserResponse , ValidateNameRequest , 6 }; 7 use crate :: error :: UserError ; 8 use crate :: repository ::{ 9 RepositoryRepository , RepositoryRepositoryImpl , UserRepository , UserRepositoryImpl , 1 use async_trait ::async_trait; 2 3 use crate :: dto ::{ 4 GetCurrentUserRequest , GetUserRequest , HasUserRequest , ListUserRepositoriesRequest , 5 RepositoryResponse , UserResponse , 6 }; 7 use crate :: error :: UserError ; 8 use crate :: repository ::{ 9 RepositoryRepository , RepositoryRepositoryImpl ,
11 use crate :: util :: auth ::is_reserved_name; 12 13 #[async_trait] 14 pub trait UserService : Send + Sync + ' static { 15 async fn validate_name (& self , request : ValidateNameRequest ) -> Result <(), UserError >; 16 17 async fn get_current_user (
51 where 52 U : UserRepository , 53 R : RepositoryRepository , 54 { 55 async fn validate_name (& self , request : ValidateNameRequest ) -> Result <(), UserError > { 56 let name = request .name. to_string (); 57 58 if is_reserved_name (& 1 import { redirect } from " next/navigation " ; 2 import { getCurrentUser } from " @/lib/dal " ; 3 import AuthorizeDeviceForm from " ../../ui/authorize-device-form " ; 4 5 export default function Page () { 6 return ( .. .. 1 import AuthorizeDeviceForm from " ../../ui/authorize-device-form " ; 2 3 export default function Page () { 4 return ( 1 " use client " ; 2 3 import { login } from " @/actions " ; 4 import { useIsTyping } from " @/hooks/use-is-typing " ; 5 import { cn , validateEmail , validatePassword } from " @/util " ; 6 import { useActionState , useEffect , useState } from " react " ; .. .. 12 const [ canSubmit , setCanSubmit ] = useState ( false ); 13 const isTyping = useIsTyping ( email , password ); 14 15 useEffect ( () => { 1 " use client " ; 2 3 import { useActionState , useEffect , useState , useRef } from " react " ; 4 import { login } from " @/actions " ; 5 import { useIsTyping } from " @/hooks/use-is-typing " ; .. 3
29 name = " email " 30 placeholder = " Email " 31 value ={ email } 32 onChange ={( e ) => setEmail ( e . target . value ) } 33 className = " border-border border-b mb-2 ring-0 outline-0 focus:border-black transition-colors duration-150 " .. .. .. 1 " use client " ; 2 3 import { signup } from " @/actions " ; 4 import { cn , validateEmail , validateName } from " @/util " ; 5 import { useActionState , useState } from " react " ; .. .. 6 7 export default function SignupForm () { 8 const [ email , setEmail ] = useState ( "" ); 9 const [ name , setName ] = useState ( " " ); 10 11 const [ state , formAction , isPending ] = useActionState ( signup , null ); .. .. .. .. 12 const canSubmit = validateEmail ( email ) && validateName ( name ) && ! isPending ; 13 14 if ( state && " success " in state ) { 15 return ( 16 <div className = " flex flex-col text-sm w-sm " > 2 3 import { useEffect , useActionState , useState , useRef } from " react " ; 4 import { signup } from " @/actions " ; 5 import { cn , validateEmail , validateUsername } from " @/util " ; 6 import { useIsTyping }
23 ); 24 } 25 26 return ( 27 <form action ={ formAction } className = " flex flex-col text-sm w-sm " > 28 <p className = " pb-2 " > Sign up. </p> 29 30 <input 31 type = " email "
37 /> 38 39 <input 40 type = " text " 41 name = " name " 42 placeholder = " Username " 43 value ={ name } 44 onChange ={( e ) => setName ( e . target . value )
53 </div> 54 <button 55 type = " submit " 56 className ={ cn ( 57 " underline transition - all duration - 300 " , 58 canSubmit 59 ? " cursor - pointer decoration - current " 1 import fs from " fs " ; 2 import path from " path " ; 3 import matter from " gray-matter " ; 4 5 const postsDirectory = path . join ( process . cwd (), " app/(blog)/content/week " ); 6 1 import fs from " node : fs " ; 2 import path from " node : path " ; 3 import matter from " gray-matter " ; 4 5 const postsDirectory = path . join ( process . cwd (), " app/(blog)/content/week " ); 6
67 const fileNames = fs . readdirSync ( postsDirectory ); 68 return fileNames 69 . filter ( ( fileName ) => fileName . endsWith ( " .md " )) 70 . map ( ( fileName ) => parseInt ( fileName . replace ( " .md " , "" ), 10 )) 71 2 import { Metadata } from " next " ; 3 import Link from " next/link " ; 4 import { notFound } from " next/navigation " ; 5 import { getAllWeeks , getPostByWeek } from " ../../lib/posts " ; 6 import MarkdownContent from " ../ui/markdown-content " ; 1 import type { Metadata } from " next " ; 2 import Link from " next/link " ; 3 import { notFound } from " next/navigation " ; 4 import { SubscribeButton } from " @/(landing)/ui/subscribe-button " ; 5 import { getAllWeeks , getPostByWeek } from " ../../lib/posts " ; 1 import { Metadata } from " next " ; 2 import Link from " next/link " ; 3 import { getAllPosts } from " ../lib/posts " ; 4 5 export const metadata : Metadata = { 1 import type { Metadata } from " next " ; 2 import Link from " next/link " ; 3 import { getAllPosts } from " ../lib/posts " ; 4 5 export const metadata : Metadata = { 1 import { fileToHast } from " @/(main)/[owner]/[repo]/util " ; 2 import type { RepositoryFile } from " @/lib/dto " ; .. .. 3 import { toJsxRuntime } from " hast-util-to-jsx-runtime " ; 4 import type { JSX } from " react " ; 5 import { Fragment } from " react " ; 6 import { jsx , jsxs } from " react/jsx-runtime " ; .. .. 5 import { fileToHast } from " @/(main)/[owner]/[repo]/util " ; 6 import type { RepositoryFile } from " @/lib/dto " ; 7 import type { LineSelection } from " ../util " ; 8 import { FileLine } from " ./file-line 1 " use client " ; 2 3 import { MoreHorizontal , MoreVertical } from " lucide-react " ; 4 import { useState } from " react " ; 5 import type { AnswerResponse } from " @/lib/dto " ; 6 import { Button } from " @/ui/button " ; 7 import { 1 " use client " ; 2 3 import { MoreHorizontal } from " lucide-react " ; 4 import { useState } from " react " ; 5 import type { AnswerResponse } from " @/lib/dto " ; 6 import { Button } from " @/ui/button " ; 7 import { 46 placeholder = " Write comment... " 47 autoFocus 48 value ={ body } 49 onChange ={( e ) => setBody ( e . target . value ) } 50 onBlur ={( e ) => { 51 setBody ( "" ); 52 setShowInput ( false ); 53 }} 54 onKeyDown ={( e ) => { 46 placeholder = " Write comment... " 47 autoFocus 48 value ={ body } 49 onChange ={( e ) => setBody ( e . target . value ) } 50 onBlur ={( _e ) => { 51 setBody ( "" ); 52 setShowInput ( false ); 53 }} 54 1 " use client " ; 2 3 import { MoreHorizontal , MoreVertical } from " lucide-react " ; 4 import { useState } from " react " ; 5 import type { QuestionResponse } from " @/lib/dto " ; 6 import { Button } from " @/ui/button " ; 7 import { 1 " use client " ; 2 3 import { MoreHorizontal } from " lucide-react " ; 4 import { useState } from " react " ; 5 import type { QuestionResponse } from " @/lib/dto " ; 6 import { Button } from " @/ui/button " ; 7 import { 1 mod get_current_user; 2 mod get_user; 3 mod list_user_repositories ; 4 mod validate_name ; 5 6 use axum ::{ Router , routing ::get}; 7 8 use crate :: app :: AppState ; 9 10 use get_current_user ::get_current_user; 11 use get_user ::get_user; 12 use list_user_repositories ::list_user_repositories ; 13 use validate_name ::validate_name ; 14 15 pub fn create_user_router () -> Router < AppState > { 16 Router :: new () 17 . route ( " /user " , get ( get_current_user )) 18 . route ( " /user/ { user_name }" , get ( get_user ). head ( validate_name )) 19 . route ( 20 " /user/ { user_name } /repositories " , 21 get ( list_user_repositories ), 22 ) 1 mod get_current_user; 2 mod get_user; 3 mod has_user ; 4 mod list_user_repositories ; 5 6 use axum ::{ Router , routing ::get}; 7 8 use crate :: app :: AppState ; 9 10 use get_current_user ::get_current_user; 11 use get_user ::get_user; 12 2 extract ::{ Path , State }, 3 http :: StatusCode , 4 }; 5 6 use gitdot_core :: dto :: ValidateNameRequest ; 7 8 use crate :: app ::{ AppError , AppResponse , AppState }; 9 10 pub async fn validate_name ( 11 State ( state ): State < AppState >, 12 Path ( user_name ): Path < String >, 13 ) -> Result < AppResponse <()>, AppError > { 14 let validate_request = ValidateNameRequest :: new (& user_name )?; 15 state 16 .user_service 17 . validate_name ( validate_request ) 18 . await 19 . map_err ( AppError :: from ) 20 . map ( | _ | AppResponse :: new ( StatusCode :: OK , ())) 21 } 2 extract ::{ Path , State }, 3 http :: StatusCode , 4 }; 5 6 use gitdot_core :: dto :: HasUserRequest ; 7 8 use crate :: app ::{ AppError , AppResponse , AppState }; 9 10 pub async fn has_user ( 11 State ( 3 use nutype ::nutype; 4 5 fn is_valid_slug ( s : & str ) -> bool { 6 ! s . is_empty () 7 && s . len () <= 100 8 && s . chars () 9 . all ( | c | c . is_ascii_lowercase () || c . is_ascii_digit () || c == ' - ' || c == ' _ ' ) 10 && ! s . starts_with ( ' - ' ) 11 && ! s . ends_with ( ' - ' ) 4 5 fn is_valid_slug ( s : & str ) -> bool { 6 ! s . is_empty () 7 && s . len ( ) > 1 8 && s . len () <= 32 9 && s . chars () 10 . all ( | c | c . is_ascii_lowercase () use
has_user
::has_user ;
13 use list_user_repositories ::list_user_repositories ;
14
15 pub fn create_user_router () -> Router < AppState > {
16 Router :: new ()
17 . route ( " /user " , get ( get_current_user ))
18 . route ( " /user/ { user_name }" , get ( get_user ). head ( has_user ))
19 . route (
20 " /user/ { user_name } /repositories " ,
21 get ( list_user_repositories ),
22 )
state
):
State
<
AppState
>,
12 Path ( user_name ): Path < String >,
13 ) -> Result < AppResponse <()>, AppError > {
14 let request = HasUserRequest :: new (& user_name )?;
15 state
16 .user_service
17 . has_user ( request )
18 . await
19 . map_err ( AppError :: from )
20 . map ( | _ | AppResponse :: new ( StatusCode :: OK , ()))
21 }
||
c
.
is_ascii_digit
()
||
c
==
'
-
'
||
c
==
'
_
'
)
11 && ! s . starts_with ( ' - ' )
12 && ! s . ends_with ( ' - ' )
Debug
,
Clone
)]
17 pub struct UserResponse {
18 pub id : Uuid ,
9 use crate :: model :: User ; 10 11 pub use get_current_user :: GetCurrentUserRequest ; 12 pub use get_user :: GetUserRequest ; 13 pub use has_user :: HasUserRequest ; 14 pub use list_user_repositories :: ListUserRepositoriesRequest ; 15 16 #[derive( Debug , Clone )] 17 pub struct UserResponse { 18 pub id : Uuid , name
: &
str
) ->
Result
<
Self
,
UserError
> {
11 Ok ( Self {
12 name : OwnerName :: try_new ( name )
13 . map_err ( | e | UserError :: InvalidUserName ( e . to_string ()))?,
UserRepository
,
UserRepositoryImpl
,
18
&
self
,
19 request : GetCurrentUserRequest ,
11 use crate :: util :: auth ::is_reserved_name; 12 13 #[async_trait] 14 pub trait UserService : Send + Sync + ' static { 15 async fn has_user (& self , request : HasUserRequest ) -> Result <(), UserError >; 16 17 async fn get_current_user ( 18 & self , 19 request : GetCurrentUserRequest , name
) {
59 return Err ( UserError :: ReservedName ( name ) ) ;
60 }
61
62 if self .user_repo. is_name_taken (& name ). await ? {
63 return Err ( UserError :: NameTaken ( name ));
64 }
65
66 Ok (())
67 }
68
69 async fn get_current_user (
70 & self ,
51 where 52 U : UserRepository , 53 R : RepositoryRepository , 54 { 55 async fn has_user (& self , request : HasUserRequest ) -> Result <(), UserError > { 56 let name = request .name. to_string (); 57 58 if is_reserved_name (& name ) || self .user_repo. is_name_taken (& name ). await ? { .. .. .. .. 59 return Ok (()); .. 60 } 61 Err ( UserError :: NotFound ( name )) 62 } 63 64 async fn get_current_user ( 65 & self , import
{
useActionState
,
useEffect
,
useState
,
useRef
}
from
"
react
"
;
14 const passwordRef = useRef < HTMLInputElement > ( null ) ;
15
16 useEffect ( () => {
17 if ( ! isTyping ) {
18 setCanSubmit (
..
..
..
..
..
..
..
..
34 />
35
36 <input
37 type = " password "
29 type = " email " 30 name = " email " 31 placeholder = " Email " 32 value ={ email } 33 onChange ={( e ) => setEmail ( e . target . value ) } 34 onKeyDown = { ( e ) => { 35 if ( e . key === ' Enter ' ) { 36 e . preventDefault ( ) ; 37 passwordRef . current ?. focus ( ) ; 38 } 39 } } 40 className = " border-border border-b mb-2 ring-0 outline-0 focus:border-black transition-colors duration-150 " 41 /> 42 43 <input 44 ref = { passwordRef } 45 type = " password " 46 name = " password " 47 placeholder = " Password " 48 value ={ password } from
"
@/hooks/use-is-typing
"
;
7
8 export default function SignupForm () {
9 const [ state , formAction , isPending ] = useActionState ( signup , null ) ;
10 const [ username , setUsername ] = useState ( " " ) ;
11 const [ email , setEmail ] = useState ( "" );
12 const [ canSubmit , setCanSubmit ] = useState ( false );
13 const isTyping = useIsTyping ( username , email ) ;
14 const usernameRef = useRef < HTMLInputElement > ( null );
15
16 useEffect ( ( ) => {
17 if ( ! isTyping ) {
18 setCanSubmit (
19 validateEmail ( email ) && validateUsername ( username ) && ! isPending ,
20 ) ;
21 }
22 } , [ email , username , isTyping , isPending ] ) ;
23
33
);
34 }
35
36 return (
37 <form action ={ formAction } className = " flex flex-col text-sm w-sm " noValidate >
38 <p className = " pb-2 " > Sign up. </p>
39
40 <input
41 type = " email "
}
45 className = " border-border border-b mb-2 ring-0 outline-0 focus:border-black transition-colors duration-150 "
46 />
47
48 <div className = " flex flex-row w-full justify-between " >
53 /> 54 55 <input 56 type = " text " 57 name = " username " 58 placeholder = " Username " 59 value ={ username } 60 onChange ={( e ) => setUsername ( e . target . value ) } 61 className = " border-border border-b mb-2 ring-0 outline-0 focus:border-black transition-colors duration-150 " 62 /> 63 64 <div className = " flex flex-row w-full justify-between " > 60 : " text-primary/60 cursor-not-allowed decoration-transparent " ,
61 ) }
62 disabled ={ isPending || email === " " || name === " " }
63 >
64 { isPending ? " Submitting... " : " Submit. " }
65 </button>
66 </div>
69 </div> 70 <button 71 type = " submit " 72 className ={ cn ( 73 " cursor - pointer underline transition - all duration - 300 " , 74 canSubmit ? " decoration - current " : " decoration-transparent " , 75 isPending ? " cursor-default " : " " , .. 76 ) } 77 disabled ={ isPending } 78 > 79 { isPending ? " Submitting... " : " Submit. " } 80 </button> 81 </div> .
filter
(
(
num
)
=>
!
isNaN
(
num
))
72 . sort ( ( a , b ) => a - b );
73 } catch ( error ) {
74 console . error ( " Error reading week numbers: " , error );
75 return [];
67 const fileNames = fs . readdirSync ( postsDirectory ); 68 return fileNames 69 . filter ( ( fileName ) => fileName . endsWith ( " .md " )) 70 . map ( ( fileName ) => parseInt ( fileName . replace ( " .md " , "" ), 10 )) 71 . filter ( ( num ) => ! Number . isNaN ( num )) 72 . sort ( ( a , b ) => a - b ); 73 } catch ( error ) { 74 console . error ( " Error reading week numbers: " , error ); 75 return []; "
;
9 import { FileViewerClient } from " ./file-viewer-client " ;
10
onKeyDown
={(
e
)
=>
{
Dialog
,
DialogContent
,
DialogTitle
}
from
"
@/ui/dialog
"
;
7 import { fuzzyMatch } from " ../../util " ;
8
9 export function RepoFileDialog ({
1 " use client " ; 2 3 import { useRouter } from " next/navigation " ; 4 import { use , useCallback , useEffect , useMemo , useRef , useState } from " react " ; 5 import type { RepositoryTreeEntry } from " @/lib/dto " ; 6 import { Dialog , DialogContent , DialogTitle } from " @/ui/dialog " ; 7 import { fuzzyMatch } from " ../../util " ; 8 9 export function RepoFileDialog ({
38 file , 39 result : fuzzyMatch ( query , file . path ), 40 } )) 41 . filter ( ({ result }) => result !== null ) 42 . sort ( ( a , b ) => b . result ! . score - a . result ! . score ) 43 . map ( ({ file }) => file ); 44 } , [ files , query ]); 45 46 const selectedFile = filteredFiles [ selectedIndex ]; 38 file , 39 result : fuzzyMatch ( query , file . path ), 40 } )) 41 . filter ( ({ result }) => result !== null ) 42 . sort ( ( a , b ) => b . result ?. score - a . result ?. score ) 43 . map ( ({ [
...
hunks
]
.
sort
(
(
a
,
b
)
=>
{
25 return getStartingLine ( a ) - getStartingLine ( b );
17 const getStartingLine = ( hunk : DiffHunk ): number => { 18 const firstLine = hunk [ 0 ]; 19 return firstLine . lhs 20 ? firstLine . lhs . line_number 21 : firstLine . rhs ?. line_number ; 22 } ; 23 24 const sortedHunks = [ ... hunks ] . sort ( ( a , b ) => { 25 return getStartingLine ( a ) - getStartingLine ( b );
102 if ( line . lhs ) { 103 const firstLhs = firstLine . lhs ?. line_number ?? line . lhs . line_number ; 104 return [ line . lhs . line_number , firstLhs - 1 ]; 105 } else { 106 const firstRhs = firstLine . rhs ?. line_number ?? line . rhs ! . line_number ; 107 return [ firstRhs - 1 , line . rhs ! . line_number ]; 108 } 109 } 110 111 /** 102 if ( line . lhs ) { 103 const firstLhs = firstLine . lhs ?. line_number ?? line . lhs . line_number ; 104 return [ line . lhs . line_number , firstLhs - 1 ]; 105 } else { 106 const firstRhs = firstLine . rhs ?. line_number ?? line . rhs ?. line_number ; }
from
"
@/ui/dialog
"
;
7 import { useUser } from " ../providers/user-provider " ;
8
9 export function AuthBlockerDialog ({
1 " use client " ; 2 3 import { VisuallyHidden } from " @radix-ui/react-visually-hidden " ; 4 import { useState , useTransition } from " react " ; 5 import { login , signup } from " @/actions " ; 6 import { Dialog , DialogContent , DialogTitle } from " @/ui/dialog " ; 7 import { useUser } from " ../providers/user-provider " ; 8 9 export function AuthBlockerDialog ({
44 } 45 } ); 46 } ; 47 48 const toggleMode = () => { 49 setMode ( mode === " login " ? " signup " : " login " ); 50 setError ( null ); 51 } ; 52 44 } 45 } ); 46 } ; 47 48 const _toggleMode = () => { 49 setMode ( mode === " login " ? " signup " : " login " ); 50 setError ( null ); 51 } ; 52 useParams
,
usePathname
,
useRouter
}
from
"
next/navigation
"
;
..
..
1 " use client " ;
2
3 import { useUser } from " @/(main)/providers/user-provider " ;
4 import { signout } from " @/actions " ;
8 DropdownMenu , 9 DropdownMenuContent , 10 DropdownMenuItem , 11 DropdownMenuTrigger , 12 } from " @/ui/dropdown-menu " ; .. .. 3 import { User } from " lucide-react " ; 4 import { useParams , usePathname , useRouter } from " next/navigation " ; 5 import { useUser } from " @/(main)/providers/user-provider " ; 6 import { signout } from " @/actions " ; 7 import { 8 DropdownMenu , "
;
4 import { redirect } from " next/navigation " ;
5 import {
6 authorizeDevice ,
7 createAnswer ,
11 createRepository , 12 getCurrentUser , 13 updateAnswer , 14 updateComment , 15 updateQuestion , 16 validateUsername , 17 voteAnswer , 18 voteComment , 19 voteQuestion , 20 } from " @/lib/dal " ; 21 import { createSupabaseClient } from " @/lib/supabase " ; 22 import { refresh } from " next/cache " ; .. .. 22 import { refresh } from " next/cache " ; 23 import type { 24 AnswerResponse , 25 CommentResponse , 11 createRepository , 12 getCurrentUser , 13 hasUser , 14 updateAnswer , 15 updateComment , .. .. .. .. .. .. .. 13 hasUser , 30 import { delay , validateEmail
42 const supabase = await createSupabaseClient (); 43 const email = formData . get ( " email " ) as string ; 44 const password = formData . get ( " password " ) as string ; 45 const redirectTo = formData . get ( " redirect " ) as string ; 46 .. .. .. 47 const { error } = await supabase . auth . signInWithPassword ( { 48 email , 49 password , 50 } ); 42 const supabase = await createSupabaseClient (); 43 const email = formData . get ( " email " ) as string ; 44 const password = formData . get ( " password " ) as string ; 45 const redirectTo = formData . get ( " redirect " ) as string ; 46 47 if
59 formData : FormData , 60 ): Promise < AuthActionResult > { 61 const supabase = await createSupabaseClient (); 62 const email = formData . get ( " email " ) as string ; 63 const name = formData . get ( " name " ) as string ; .. .. .. .. 64 65 const valid = await validateUsername ( name ); 66 if ( ! valid ) { 67 return { error : " Username taken " } ; .. 68 } 69 const { data , error } = await supabase . auth . signInWithOtp ( { 70 email , 71 options : { shouldCreateUser : true } , 72 } ); 73 63 formData : FormData , 64 ): Promise < AuthActionResult > { 65 const supabase = await createSupabaseClient (); 66 const email = formData . get ( " email " ) as string ; 67 const username = formData . get ( " username " ) as string ; 68 69 type
{
Metadata
}
from
"
next
"
;
2 import { IBM_Plex_Sans , Inconsolata } from " next/font/google " ;
3 import " ./globals.css " ;
4
5 export const metadata : Metadata = {
6 title : " gitdot " ,
;
18 type VoteRequest , 19 type VoteResponse , 20 VoteResponseSchema , 21 } from " ../dto " ; 22 import { .. 26 GITDOT_SERVER_URL , 27 handleResponse , 28 } from " ./util " ; 29 30 export async function createQuestion ( 21
}
22
14 handleResponse , 15 NotFound , 16 } from " ./util " ; 17 18 export async function hasUser ( username : string ): Promise < boolean > { 19 const response = await authHead ( `${ GITDOT_SERVER_URL } /user/ ${ username }` ); 20 return response . ok ; 21 } 22 ):
boolean
{
154 } 155 return emailTester . test ( email ); 156 } 157 158 export function validateUsername ( username : string ): boolean { 159 return !! username && username . length >= 2 ; 160 } 161 162 export function validatePassword ( password : string ): boolean { file
})
=>
file
);
44 } , [ files , query ]);
45
46 const selectedFile = filteredFiles [ selectedIndex ];
107
return
[
firstRhs
-
1
,
line
.
rhs
?.
line_number
];
108 }
109 }
110
111 /**
}
from
"
./util
"
;
31
32 export async function getCurrentUserAction (): Promise < UserResponse | null > {
33 return await getCurrentUser ();
34 }
(
!
validateEmail
(
email
) )
{
48 return await delay ( 300 , { error : " Invalid email " } ) ;
49 }
50
51 const { error } = await supabase . auth . signInWithPassword ( {
52 email ,
53 password ,
if
(
!
validateEmail
(
email
) )
{
70 return await delay ( 300 , { error : " Invalid email " } )
71 } ;
72
73 const usernameError = await validateUsername ( username );
74 if ( usernameError ) {
75 return { error : usernameError } ;
76 }
77
78 const { error } = await supabase . auth . signInWithOtp ( {
79 email ,
80 options : { shouldCreateUser : true } ,
81 } );
82