Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 54 additions & 10 deletions db.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,56 @@
{
"tweets": [
{"id": "1", "author_id": "johnsmith", "text": "What are the pros of object-oriented programming?<br><br>Please, explain as if I'm 5."},
{"id": "2", "author_id": "carlanotarobot", "text": "Why use a debugger when you can fill your code with hundreds of <code>print()</code> statements?"},
{"id": "3", "author_id": "ameliawarner", "text": "Describe your relationship with JavaScript with one word."}
],
"users": [
{"id": "johnsmith", "name": "John Smith", "email": "johnsmith@gmail.com"},
{"id": "carlanotarobot", "name": "Carla Notarobot", "email": "carlanotarobot@hotmail.com"},
{"id": "ameliawarner", "name": "Amelia Warner", "email": "ameliawarner@yahoo.com"}
]
"tweets": [
{
"id": "1",
"author_id": "johnsmith",
"text": "What are the pros of object-oriented programming?<br><br>Please, explain as if I'm 5."
},
{
"id": "2",
"author_id": "carlanotarobot",
"text": "Why use a debugger when you can fill your code with hundreds of <code>print()</code> statements?"
},
{
"id": "3",
"author_id": "ameliawarner",
"text": "Describe your relationship with JavaScript with one word."
},
{
"id": "",
"author_id": "sambirboy",
"text": "Hellaos asda sd"
},
{
"id": "",
"author_id": "sambirboy",
"text": "asfasfasf <h1>hello</h1>"
},
{
"id": "",
"author_id": "sambirboy",
"text": "asfasfasf <h1 style=\"color:red\">hello</h1>"
}
],
"users": [
{
"id": "johnsmith",
"name": "John Smith",
"email": "johnsmith@gmail.com"
},
{
"id": "carlanotarobot",
"name": "Carla Notarobot",
"email": "carlanotarobot@hotmail.com"
},
{
"id": "ameliawarner",
"name": "Amelia Warner",
"email": "ameliawarner@yahoo.com"
},
{
"id": "sambirboy",
"email": "lanos@gmail.com",
"name": "sambir boy"
}
]
}
1 change: 1 addition & 0 deletions twitter-clone/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEXT_PUBLIC_BACKEND_URL='http://localhost:5000'
13 changes: 13 additions & 0 deletions twitter-clone/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": [
"next/core-web-vitals",
"next/typescript"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": "error",
"react/no-unescaped-entities": "off"
}
}
1 change: 1 addition & 0 deletions twitter-clone/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/node_modules
/.pnp
.pnp.js
/.next

# testing
/coverage
Expand Down
5 changes: 5 additions & 0 deletions twitter-clone/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"tabWidth": 4,
"semi": false,
"singleQuote": true
}
46 changes: 0 additions & 46 deletions twitter-clone/README.md

This file was deleted.

137 changes: 137 additions & 0 deletions twitter-clone/app/(no-session)/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
'use client'
import { getUserByLogin } from '@shared/api'
import { routes } from '@shared/consts'
import {
Button,
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
LabeledInput,
} from '@shared/ui'
import { cn } from '@shared/utils'
import { useFormik } from 'formik'
import Cookies from 'js-cookie'
import { LoaderCircle } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { FC, useState } from 'react'
import { object, string } from 'yup'

const Page: FC = () => {
const { replace } = useRouter()
const [error, setError] = useState<string | null>(null)
const {
isValid,
isSubmitting,
values,
touched,
errors,
handleChange,
handleBlur,
handleSubmit,
} = useFormik({
initialValues: {
nickname: '',
password: '',
},
validationSchema: object({
nickname: string()
.required('Required')
.min(1)
.max(256, 'Nickname should not longer than 256 chars'),
password: string()
.required('Required')
.min(8, 'Password should be at least 8 chars')
.max(256, 'Password should not longer than 256 chars'),
}),
isInitialValid: false,
onSubmit: async ({ nickname }, { setSubmitting }) => {
setSubmitting(true)
getUserByLogin(nickname)
.then((res) => {
if (res.ok) {
Cookies.set('userId', nickname, {
expires: new Date(
Date.now() + 1000 * 60 * 60 * 24 * 7,
),
})
replace(routes.auth.feed)
} else {
if (res.status === 404) {
setError('Invalid email or password!')
} else {
setError('Something went wrong!')
}
}
})
.catch(() => {
setError('Something went wrong!')
})
.finally(() => {
setSubmitting(false)
})
},
})

return (
<Card
asChild
className="w-[400px] absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2"
>
<form onSubmit={handleSubmit}>
<CardHeader>
<CardTitle>Login</CardTitle>
<CardDescription
className={cn(error && 'text-destructive')}
>
{error ? error : 'Login into your account'}
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-4">
<LabeledInput
name="nickname"
onChange={handleChange}
onBlur={handleBlur}
value={values.nickname}
touched={touched.nickname}
error={errors.nickname}
label="Nickname"
placeholder="johnsmith"
/>
<LabeledInput
name="password"
onChange={handleChange}
onBlur={handleBlur}
value={values.password}
touched={touched.password}
error={errors.password}
label="Password"
type="password"
placeholder="********"
/>
<Button
disabled={isSubmitting || !isValid}
type="submit"
>
{isSubmitting && (
<LoaderCircle className="animate-spin mr-2" />
)}
Login
</Button>
<Link
href={routes.unAuth.signup}
className="m-auto hover:underline text-sm"
>
Don't have an account?
</Link>
</div>
</CardContent>
</form>
</Card>
)
}

export default Page
Loading