chore: initial commit

This commit is contained in:
Divlo
2021-10-24 05:19:39 +02:00
commit 21123c4477
145 changed files with 48821 additions and 0 deletions

23
pages/404.tsx Normal file
View File

@ -0,0 +1,23 @@
import { GetStaticProps } from 'next'
import useTranslation from 'next-translate/useTranslation'
import { Head } from 'components/Head'
import { ErrorPage } from 'components/ErrorPage'
const Error404: React.FC = () => {
const { t } = useTranslation()
return (
<>
<Head title='Thream | 404' />
<ErrorPage message={t('errors:page-not-found')} statusCode={404} />
</>
)
}
export const getStaticProps: GetStaticProps = async () => {
return { props: {} }
}
export default Error404

23
pages/500.tsx Normal file
View File

@ -0,0 +1,23 @@
import { GetStaticProps } from 'next'
import useTranslation from 'next-translate/useTranslation'
import { Head } from 'components/Head'
import { ErrorPage } from 'components/ErrorPage'
const Error500: React.FC = () => {
const { t } = useTranslation()
return (
<>
<Head title='Thream | 500' />
<ErrorPage message={t('errors:server-error')} statusCode={500} />
</>
)
}
export const getStaticProps: GetStaticProps = async () => {
return { props: {} }
}
export default Error500

101
pages/_app.tsx Normal file
View File

@ -0,0 +1,101 @@
import { useEffect } from 'react'
import { AppProps } from 'next/app'
import useTranslation from 'next-translate/useTranslation'
import { ThemeProvider } from 'contexts/Theme'
import { cookies } from 'utils/cookies'
import '@fontsource/poppins/400.css'
import '@fontsource/poppins/600.css'
import '@fontsource/poppins/700.css'
import '@fontsource/roboto/400.css'
import '@fontsource/roboto/700.css'
import 'normalize.css/normalize.css'
const ThreamApp = ({ Component, pageProps }: AppProps): JSX.Element => {
const { lang } = useTranslation()
useEffect(() => {
cookies.set('NEXT_LOCALE', lang)
}, [lang])
return (
<>
<ThemeProvider>
<Component {...pageProps} />
</ThemeProvider>
<style jsx global>
{`
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
font-size: 62.5%; /* 1 rem = 10px; 10px/16px = 62.5% */
}
body {
--default-font-size: 1.6rem;
--color-background-primary: #212121;
--color-background-secondary: #292d3e;
--color-background-tertiary: #202331;
--color-primary: #27b05e;
--color-secondary: #fff;
--color-tertiary: #7c818f;
--color-success: #90ee90;
--color-error: #ff7f7f;
--color-shadow: rgba(255, 255, 255, 0.2);
&.theme-dark {
--color-background-primary: #212121;
--color-secondary: #fff;
--color-success: #90ee90;
--color-error: #ff7f7f;
--color-shadow: rgba(255, 255, 255, 0.2);
}
&.theme-light {
--color-background-primary: #fff;
--color-secondary: #181818;
--color-success: #1e4620;
--color-error: #ee1b1b;
--color-shadow: rgba(0, 0, 0, 0.2);
}
display: flex;
flex-flow: column wrap;
background-color: var(--color-background-primary);
color: var(--color-secondary);
font-size: var(--default-font-size);
font-family: 'Poppins', 'Arial', 'sans-serif';
font-weight: 400;
overflow-y: hidden;
}
#__next {
max-width: 100%;
}
a {
font-family: 'Roboto', 'Arial', 'sans-serif';
font-size: 14px;
color: var(--color-primary);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
::-webkit-scrollbar {
width: 0.5rem;
}
::-webkit-scrollbar-track {
background-color: rgba(36, 38, 45, 0.4);
}
::-webkit-scrollbar-thumb {
background-color: var(--color-background-secondary);
}
`}
</style>
</>
)
}
export default ThreamApp

View File

@ -0,0 +1,44 @@
import { Head } from 'components/Head'
import { authenticationFromServerSide } from 'utils/authentication'
import { Application, ApplicationProps } from 'components/Application'
import { Messages } from 'components/Messages'
import { Messages as MessagesType, MessagesProvider } from 'contexts/Messages'
import { Guild } from 'contexts/Guilds'
import { SendMessage } from 'components/SendMessage'
export interface ChannelPageProps extends ApplicationProps {
guild: Guild
messages: MessagesType
channelId: string
guildId: string
}
const ChannelPage: React.FC<ChannelPageProps> = (props) => {
return (
<>
<Head title={props.guild.name} />
<Application authentication={props.authentication} guilds={props.guilds}>
<MessagesProvider messages={props.messages} channelId={props.channelId}>
<Messages />
<SendMessage channelId={props.channelId} />
</MessagesProvider>
</Application>
</>
)
}
export const getServerSideProps = authenticationFromServerSide({
shouldBeAuthenticated: true,
fetchData: async (api, context) => {
const { channelId, guildId } = context.params as {
channelId: string
guildId: string
}
const { data: guild } = await api.get(`/guilds/${guildId}`)
const { data: messages } = await api.get(`/channels/${channelId}/messages`)
const { data: guilds } = await api.get('/guilds')
return { guild, guilds, messages, channelId, guildId }
}
})
export default ChannelPage

View File

@ -0,0 +1,24 @@
import { Head } from 'components/Head'
import { authenticationFromServerSide } from 'utils/authentication'
import { Application, ApplicationProps } from 'components/Application'
const ApplicationPage: React.FC<ApplicationProps> = (props) => {
return (
<>
<Head title='Thream | Application' />
<Application authentication={props.authentication} guilds={props.guilds}>
<p>Main Content</p>
</Application>
</>
)
}
export const getServerSideProps = authenticationFromServerSide({
shouldBeAuthenticated: true,
fetchData: async (api) => {
const { data } = await api.get('/guilds')
return { guilds: data }
}
})
export default ApplicationPage

View File

@ -0,0 +1,68 @@
import Link from 'next/link'
import { Input } from 'components/design/Input'
import { Head } from 'components/Head'
import { Header } from 'components/Header'
import { FormState } from 'components/Authentication/FormState'
import { Container } from 'components/design/Container'
import { AuthenticationFormLayout } from 'components/Authentication/AuthenticationFormLayout'
import { emailSchema } from 'components/Authentication/AuthenticationForm'
import { useForm } from 'hooks/useForm'
import { api } from 'utils/api'
import { authenticationFromServerSide } from 'utils/authentication'
import useTranslation from 'next-translate/useTranslation'
const ForgotPassword: React.FC = () => {
const {
getErrorMessages,
formState,
message,
handleChange,
handleSubmit
} = useForm({
validatorSchema: emailSchema
})
const { t } = useTranslation()
return (
<>
<Head title={`Thream | ${t('authentication:forgot-password')}`} />
<Header />
<Container>
<AuthenticationFormLayout
onChange={handleChange}
onSubmit={handleSubmit(async (formData) => {
await api.post(
`/users/resetPassword?redirectURI=${window.location.origin}/authentication/reset-password`,
formData
)
return await t('authentication:success-forgot-password')
})}
link={
<p>
<Link href='/authentication/signin'>
<a>{t('authentication:already-know-password')}</a>
</Link>
</p>
}
>
<Input
errors={getErrorMessages('email')}
type='email'
placeholder='Email'
name='email'
label='Email'
/>
</AuthenticationFormLayout>
<FormState state={formState} message={message} />
</Container>
</>
)
}
export const getServerSideProps = authenticationFromServerSide({
shouldBeAuthenticated: false
})
export default ForgotPassword

View File

@ -0,0 +1,75 @@
import { useRouter } from 'next/router'
import useTranslation from 'next-translate/useTranslation'
import { Head } from 'components/Head'
import { Input } from 'components/design/Input'
import { Header } from 'components/Header'
import { FormState } from 'components/Authentication/FormState'
import { Container } from 'components/design/Container'
import { AuthenticationFormLayout } from 'components/Authentication/AuthenticationFormLayout'
import { passwordSchema } from 'components/Authentication/AuthenticationForm'
import { useForm } from 'hooks/useForm'
import { api } from 'utils/api'
import { authenticationFromServerSide } from 'utils/authentication'
const ResetPassword: React.FC = () => {
const router = useRouter()
const { t } = useTranslation()
const {
getErrorMessages,
formState,
message,
handleChange,
handleSubmit
} = useForm({
validatorSchema: passwordSchema
})
return (
<>
<Head title={`Thream | ${t('authentication:reset-password')}`} />
<Header />
<Container>
<AuthenticationFormLayout
onChange={handleChange}
onSubmit={handleSubmit(async (formData) => {
await api.put('/users/resetPassword', {
...formData,
tempToken: router.query.tempToken
})
await router.push('/authentication/signin')
return null
})}
>
<Input
errors={getErrorMessages('password')}
type='password'
placeholder='Password'
name='password'
label='Password'
/>
</AuthenticationFormLayout>
<FormState state={formState} message={message} />
</Container>
<style jsx>
{`
.text-center {
text-align: center;
}
.signin-link {
font-size: 16px;
}
`}
</style>
</>
)
}
export const getServerSideProps = authenticationFromServerSide({
shouldBeAuthenticated: false
})
export default ResetPassword

View File

@ -0,0 +1,38 @@
import { useRouter } from 'next/router'
import useTranslation from 'next-translate/useTranslation'
import { Head } from 'components/Head'
import { Authentication as AuthenticationComponent } from 'components/Authentication'
import { api } from 'utils/api'
import {
Authentication,
authenticationFromServerSide,
Tokens
} from 'utils/authentication'
const Signin: React.FC = () => {
const router = useRouter()
const { t } = useTranslation()
return (
<>
<Head title={`Thream | ${t('authentication:signin')}`} />
<AuthenticationComponent
mode='signin'
onSubmit={async (formData) => {
const { data } = await api.post<Tokens>('/users/signin', formData)
const authentication = new Authentication(data)
authentication.signin()
await router.push('/application')
return null
}}
/>
</>
)
}
export const getServerSideProps = authenticationFromServerSide({
shouldBeAuthenticated: false
})
export default Signin

View File

@ -0,0 +1,34 @@
import useTranslation from 'next-translate/useTranslation'
import { Head } from 'components/Head'
import { Authentication } from 'components/Authentication'
import { api } from 'utils/api'
import { useTheme } from 'contexts/Theme'
import { authenticationFromServerSide } from 'utils/authentication'
const Signup: React.FC = () => {
const { theme } = useTheme()
const { t, lang } = useTranslation()
return (
<>
<Head title={`Thream | ${t('authentication:signup')}`} />
<Authentication
mode='signup'
onSubmit={async (formData) => {
await api.post(
`/users/signup?redirectURI=${window.location.origin}/authentication/signin`,
{ ...formData, language: lang, theme }
)
return await t('authentication:success-signup')
}}
/>
</>
)
}
export const getServerSideProps = authenticationFromServerSide({
shouldBeAuthenticated: false
})
export default Signup

138
pages/index.tsx Normal file
View File

@ -0,0 +1,138 @@
import { GetStaticProps } from 'next'
import Link from 'next/link'
import Image from 'next/image'
import useTranslation from 'next-translate/useTranslation'
import Translation from 'next-translate/Trans'
import { Head } from 'components/Head'
import { Header } from 'components/Header'
import { Container } from 'components/design/Container'
import { Button } from 'components/design/Button'
import { SocialMediaButton } from 'components/design/SocialMediaButton'
const Home: React.FC = () => {
const { t } = useTranslation()
return (
<>
<Head />
<Header />
<Container className='home-container'>
<section id='about'>
<div className='application__image'>
<Link href='/authentication/signup'>
<a>
<Image
width={351}
height={341}
src='/images/svg/design/home.svg'
alt={"Thream's chat app"}
/>
</a>
</Link>
</div>
<div>
<h1 className='title'>Thream</h1>
<div className='paragraph'>
<Translation
i18nKey='home:description'
components={[<strong key='bold' />]}
/>
<div className='buttons'>
<Link href='/authentication/signup'>
<a className='get-started-link'>
<Button>{t('home:get-started')}</Button>
</a>
</Link>
<a
href='https://github.com/Thream'
target='_blank'
rel='noopener noreferrer'
className='github-link'
>
<SocialMediaButton socialMedia='GitHub' />
</a>
</div>
</div>
</div>
</section>
</Container>
<style jsx global>
{`
@media (max-width: 338px) {
.get-started-link {
margin: 0 !important;
}
.get-started-link > button {
margin-bottom: 10px !important;
}
}
.home-container {
align-items: center;
margin-top: 50px;
text-align: justify;
margin-bottom: 20px;
margin-top: 0;
}
`}
</style>
<style jsx>
{`
#about {
display: flex;
justify-content: center;
align-items: center;
width: 740px;
flex-direction: column;
text-align: center;
width: 80%;
}
.application__image {
margin-right: 25px;
max-width: 300px;
}
.title {
font-weight: 400;
}
.paragraph {
font-family: 'Roboto', 'sans-serif';
font-size: 18px;
font-weight: 400;
line-height: 32px;
max-width: 500px;
}
strong {
font-weight: 700;
color: var(--color-primary);
}
.buttons {
text-align: center;
margin-top: 20px;
}
.get-started-link {
margin: 0 10px 0 0;
}
.github-link {
text-decoration: none;
}
@media (max-width: 600px) and (max-height: 700px) {
.application__image:first-child {
width: 65%;
}
.paragraph {
font-size: 16px;
}
}
`}
</style>
</>
)
}
export const getStaticProps: GetStaticProps = async () => {
return { props: {} }
}
export default Home