fix: improve overall rendering v3 (#25)
Follow-up of #15 Co-authored-by: Walidoux <ma.walidkorchi@icloud.com>
This commit is contained in:
		| @@ -1,15 +1,15 @@ | ||||
| FROM node:16.14.0 AS dependencies | ||||
| FROM node:16.14.2 AS dependencies | ||||
| WORKDIR /usr/src/app | ||||
| COPY ./package*.json ./ | ||||
| RUN npm install | ||||
|  | ||||
| FROM node:16.14.0 AS builder | ||||
| FROM node:16.14.2 AS builder | ||||
| WORKDIR /usr/src/app | ||||
| COPY ./ ./ | ||||
| COPY --from=dependencies /usr/src/app/node_modules ./node_modules | ||||
| RUN npm run build | ||||
|  | ||||
| FROM node:16.14.0 AS runner | ||||
| FROM node:16.14.2 AS runner | ||||
| WORKDIR /usr/src/app | ||||
| ENV NODE_ENV=production | ||||
| COPY --from=builder /usr/src/app/next.config.js ./next.config.js | ||||
|   | ||||
| @@ -18,7 +18,7 @@ | ||||
|  | ||||
| Thream's website to stay close with your friends and communities. | ||||
|  | ||||
| It uses [Thream/api](https://github.com/Thream/api) v1.0.0. | ||||
| It uses [Thream/api](https://github.com/Thream/api) [v1.0.0](https://github.com/Thream/api/releases/tag/v1.0.0). | ||||
|  | ||||
| ## ⚙️ Getting Started | ||||
|  | ||||
|   | ||||
| @@ -184,6 +184,7 @@ export const Application: React.FC<ApplicationProps> = (props) => { | ||||
|               title='Settings' | ||||
|             > | ||||
|               <Image | ||||
|                 quality={100} | ||||
|                 className='rounded-full' | ||||
|                 src={ | ||||
|                   user.logo == null | ||||
|   | ||||
| @@ -1,13 +0,0 @@ | ||||
| import { render } from '@testing-library/react' | ||||
|  | ||||
| import { channelExample } from '../../../../cypress/fixtures/channels/channel' | ||||
| import { Channel } from './Channel' | ||||
|  | ||||
| describe('<Channel />', () => { | ||||
|   it('should render successfully', () => { | ||||
|     const { baseElement } = render( | ||||
|       <Channel channel={channelExample} path={{ channelId: 1, guildId: 1 }} /> | ||||
|     ) | ||||
|     expect(baseElement).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
| @@ -1,8 +1,12 @@ | ||||
| import classNames from 'classnames' | ||||
| import Link from 'next/link' | ||||
| import { useRouter } from 'next/router' | ||||
| import { CogIcon } from '@heroicons/react/solid' | ||||
|  | ||||
| import { GuildsChannelsPath } from '../../Application' | ||||
| import { Channel as ChannelType } from '../../../../models/Channel' | ||||
| import { useGuildMember } from '../../../../contexts/GuildMember' | ||||
| import { IconButton } from '../../../design/IconButton' | ||||
|  | ||||
| export interface ChannelProps { | ||||
|   path: GuildsChannelsPath | ||||
| @@ -12,20 +16,39 @@ export interface ChannelProps { | ||||
|  | ||||
| export const Channel: React.FC<ChannelProps> = (props) => { | ||||
|   const { channel, path, selected = false } = props | ||||
|   const router = useRouter() | ||||
|  | ||||
|   const { member } = useGuildMember() | ||||
|  | ||||
|   return ( | ||||
|     <Link href={`/application/${path.guildId}/${channel.id}`}> | ||||
|       <a | ||||
|         className={classNames( | ||||
|           'group my-3 mx-3 flex items-center justify-between rounded-lg py-2 text-sm transition-colors duration-200 hover:bg-gray-100 dark:hover:bg-gray-600', | ||||
|           'group relative my-3 mx-3 flex items-center justify-between overflow-hidden rounded-lg py-2 text-sm transition-all duration-200 hover:bg-gray-100 dark:hover:bg-gray-600', | ||||
|           { | ||||
|             'font-semibold text-green-800 dark:text-green-400': selected | ||||
|           } | ||||
|         )} | ||||
|       > | ||||
|         <span className='ml-2 mr-4' data-cy='channel-name'> | ||||
|         <span | ||||
|           className='max-[315px] ml-2 mr-4 break-all' | ||||
|           data-cy='channel-name' | ||||
|         > | ||||
|           # {channel.name} | ||||
|         </span> | ||||
|         {member.isOwner && ( | ||||
|           <IconButton | ||||
|             onClick={async () => { | ||||
|               await router.push( | ||||
|                 `/application/${channel.guildId}/${channel.id}/settings` | ||||
|               ) | ||||
|             }} | ||||
|             className='bg-unherit absolute -right-10 h-full w-8 transition-all group-hover:right-0 group-hover:shadow-lg dark:group-hover:bg-gray-600' | ||||
|             title='Settings' | ||||
|           > | ||||
|             <CogIcon height={20} width={20} /> | ||||
|           </IconButton> | ||||
|         )} | ||||
|       </a> | ||||
|     </Link> | ||||
|   ) | ||||
|   | ||||
| @@ -38,6 +38,7 @@ export const ConfirmGuildJoin: React.FC<ConfirmGuildJoinProps> = ({ | ||||
|         )} | ||||
|       > | ||||
|         <Image | ||||
|           quality={100} | ||||
|           src='/images/svg/design/join-guild.svg' | ||||
|           alt='Join Guild Illustration' | ||||
|           height={150} | ||||
|   | ||||
| @@ -50,7 +50,7 @@ export const GuildSettings: React.FC = () => { | ||||
|       setInputValues(formData as any) | ||||
|       return { | ||||
|         type: 'success', | ||||
|         value: 'common:name' | ||||
|         value: 'application:saved-information' | ||||
|       } | ||||
|     } catch (error) { | ||||
|       return { | ||||
| @@ -130,6 +130,7 @@ export const GuildSettings: React.FC = () => { | ||||
|               </div> | ||||
|               <div className='flex items-center justify-center rounded-full bg-black shadow-xl'> | ||||
|                 <Image | ||||
|                   quality={100} | ||||
|                   className='rounded-full opacity-50' | ||||
|                   src={ | ||||
|                     guild.icon == null | ||||
| @@ -193,7 +194,10 @@ export const GuildSettings: React.FC = () => { | ||||
|             </Button> | ||||
|           )} | ||||
|         </div> | ||||
|         <FormState state={fetchState} message={message} /> | ||||
|         <FormState | ||||
|           state={fetchState} | ||||
|           message={getErrorTranslation(errors.description) ?? message} | ||||
|         /> | ||||
|       </div> | ||||
|     </Form> | ||||
|   ) | ||||
|   | ||||
| @@ -21,6 +21,7 @@ export const Guild: React.FC<GuildProps> = (props) => { | ||||
|     > | ||||
|       <div className='pl-[6px]'> | ||||
|         <Image | ||||
|           quality={100} | ||||
|           className='rounded-full' | ||||
|           src={ | ||||
|             guild.icon != null | ||||
|   | ||||
| @@ -61,6 +61,7 @@ export const GuildPublic: React.FC<GuildPublicProps> = (props) => { | ||||
|         onClick={handleIsConfirmed} | ||||
|       > | ||||
|         <Image | ||||
|           quality={100} | ||||
|           className='rounded-full' | ||||
|           src={ | ||||
|             guild.icon != null | ||||
|   | ||||
| @@ -33,9 +33,7 @@ export const Member: React.FC<MemberProps> = (props) => { | ||||
|             <p data-cy='member-user-name' className='truncate font-bold'> | ||||
|               {member.user.name} | ||||
|             </p> | ||||
|             {member.user.status != null && ( | ||||
|               <span className='block w-44 truncate'>{member.user.status}</span> | ||||
|             )} | ||||
|             {member.user.status != null && member.user.status} | ||||
|           </div> | ||||
|         </div> | ||||
|       </a> | ||||
|   | ||||
| @@ -25,6 +25,7 @@ export const Message: React.FC<MessageProps> = (props) => { | ||||
|           <div className='mr-4 flex h-12 w-12 flex-shrink-0 items-center justify-center'> | ||||
|             <div className='h-10 w-10 drop-shadow-md'> | ||||
|               <Image | ||||
|                 quality={100} | ||||
|                 className='rounded-full' | ||||
|                 src={ | ||||
|                   message.member.user.logo == null | ||||
|   | ||||
| @@ -23,6 +23,7 @@ export const PopupGuild: React.FC<PopupGuildProps> = (props) => { | ||||
|       <PopupGuildCard | ||||
|         image={ | ||||
|           <Image | ||||
|             quality={100} | ||||
|             src='/images/svg/design/create-guild.svg' | ||||
|             alt={t('application:create-a-guild')} | ||||
|             draggable='false' | ||||
| @@ -40,6 +41,7 @@ export const PopupGuild: React.FC<PopupGuildProps> = (props) => { | ||||
|       <PopupGuildCard | ||||
|         image={ | ||||
|           <Image | ||||
|             quality={100} | ||||
|             src='/images/svg/design/join-guild.svg' | ||||
|             alt={t('application:join-a-guild')} | ||||
|             draggable='false' | ||||
|   | ||||
| @@ -10,6 +10,7 @@ describe('<PopupGuildCard />', () => { | ||||
|       <PopupGuildCard | ||||
|         image={ | ||||
|           <Image | ||||
|             quality={100} | ||||
|             src='/images/svg/design/create-server.svg' | ||||
|             alt='' | ||||
|             width={230} | ||||
|   | ||||
| @@ -104,7 +104,6 @@ export const SendMessage: React.FC<SendMessageProps> = (props) => { | ||||
|             onChange={handleTextareaChange} | ||||
|             value={message} | ||||
|             ref={textareaReference} | ||||
|             autoFocus | ||||
|           /> | ||||
|         </form> | ||||
|         <div className='flex h-full items-center justify-around pr-6'> | ||||
|   | ||||
| @@ -24,6 +24,7 @@ export const UserProfile: React.FC<UserProfileProps> = (props) => { | ||||
|             <div className='flex w-max items-center'> | ||||
|               <div className='relative flex items-center justify-center overflow-hidden rounded-full shadow-lg transition-all'> | ||||
|                 <Image | ||||
|                   quality={100} | ||||
|                   className='rounded-full' | ||||
|                   src={ | ||||
|                     user.logo != null | ||||
|   | ||||
| @@ -2,9 +2,10 @@ import Image from 'next/image' | ||||
| import useTranslation from 'next-translate/useTranslation' | ||||
| import { useState } from 'react' | ||||
| import { Form } from 'react-component-form' | ||||
| import { PhotographIcon } from '@heroicons/react/solid' | ||||
| import { EyeIcon, PhotographIcon } from '@heroicons/react/solid' | ||||
| import { Type } from '@sinclair/typebox' | ||||
| import axios from 'axios' | ||||
| import Link from 'next/link' | ||||
|  | ||||
| import { API_URL } from '../../../tools/api' | ||||
| import { Input } from '../../design/Input' | ||||
| @@ -17,7 +18,7 @@ import { useAuthentication } from '../../../tools/authentication' | ||||
| import { Button } from '../../design/Button' | ||||
| import { FormState } from '../../design/FormState' | ||||
| import { useForm, HandleSubmitCallback } from '../../../hooks/useForm' | ||||
| import { userCurrentSchema, userSchema } from '../../../models/User' | ||||
| import { userSchema } from '../../../models/User' | ||||
| import { userSettingsSchema } from '../../../models/UserSettings' | ||||
|  | ||||
| export const UserSettings: React.FC = () => { | ||||
| @@ -45,7 +46,7 @@ export const UserSettings: React.FC = () => { | ||||
|     validateSchema: { | ||||
|       name: userSchema.name, | ||||
|       status: Type.Optional(userSchema.status), | ||||
|       email: Type.Optional(userCurrentSchema.email), | ||||
|       email: Type.Optional(Type.Union([userSchema.email, Type.Null()])), | ||||
|       website: Type.Optional(userSchema.website), | ||||
|       biography: Type.Optional(userSchema.biography), | ||||
|       isPublicGuilds: userSettingsSchema.isPublicGuilds, | ||||
| @@ -63,6 +64,14 @@ export const UserSettings: React.FC = () => { | ||||
|         `/users/current?redirectURI=${window.location.origin}/authentication/signin`, | ||||
|         userData | ||||
|       ) | ||||
|       setInputValues(formData as any) | ||||
|       const hasEmailChanged = user.email !== userCurrentData.user.email | ||||
|       if (hasEmailChanged) { | ||||
|         return { | ||||
|           type: 'success', | ||||
|           value: 'application:success-email-changed' | ||||
|         } | ||||
|       } | ||||
|       const { data: userCurrentSettings } = await authentication.api.put( | ||||
|         '/users/current/settings', | ||||
|         userSettings | ||||
| @@ -74,10 +83,9 @@ export const UserSettings: React.FC = () => { | ||||
|           settings: userCurrentSettings.settings | ||||
|         } | ||||
|       }) | ||||
|       setInputValues(formData as any) | ||||
|       return { | ||||
|         type: 'success', | ||||
|         value: 'common:name' | ||||
|         value: 'application:saved-information' | ||||
|       } | ||||
|     } catch (error) { | ||||
|       if (axios.isAxiosError(error) && error.response?.status === 400) { | ||||
| @@ -185,6 +193,7 @@ export const UserSettings: React.FC = () => { | ||||
|             </div> | ||||
|             <div className='flex items-center justify-center rounded-full bg-black shadow-xl'> | ||||
|               <Image | ||||
|                 quality={100} | ||||
|                 className='rounded-full opacity-50' | ||||
|                 src={ | ||||
|                   user.logo != null | ||||
| @@ -255,6 +264,24 @@ export const UserSettings: React.FC = () => { | ||||
|           /> | ||||
|         </div> | ||||
|         <div className='flex h-full w-4/5 flex-col items-center justify-between pr-0 sm:w-[415px] lg:pl-12'> | ||||
|           <div className='flex w-full items-center pt-14'> | ||||
|             <Language className='!top-12' /> | ||||
|             <div className='ml-auto flex'> | ||||
|               <SwitchTheme /> | ||||
|               <Link href={`/application/users/${user.id}`}> | ||||
|                 <a | ||||
|                   className='group ml-3 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg bg-slate-200 transition-colors hover:bg-slate-300 dark:bg-slate-700 hover:dark:bg-slate-800' | ||||
|                   title='Preview Public Profile' | ||||
|                 > | ||||
|                   <EyeIcon | ||||
|                     height={20} | ||||
|                     className='opacity-50 transition-opacity group-hover:opacity-100' | ||||
|                   /> | ||||
|                 </a> | ||||
|               </Link> | ||||
|             </div> | ||||
|           </div> | ||||
|  | ||||
|           <div className='mt-14 flex w-full flex-col gap-4'> | ||||
|             <SocialMediaButton | ||||
|               socialMedia='Google' | ||||
| @@ -269,10 +296,6 @@ export const UserSettings: React.FC = () => { | ||||
|               className='w-full justify-center' | ||||
|             /> | ||||
|           </div> | ||||
|           <div className='flex w-full justify-between pt-14'> | ||||
|             <Language /> | ||||
|             <SwitchTheme /> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,7 @@ export const Header: React.FC = () => { | ||||
|           <a> | ||||
|             <div className='flex items-center justify-center'> | ||||
|               <Image | ||||
|                 quality={100} | ||||
|                 width={60} | ||||
|                 height={60} | ||||
|                 src='/images/icons/Thream.png' | ||||
|   | ||||
| @@ -7,7 +7,12 @@ import { Arrow } from './Arrow' | ||||
| import { LanguageFlag } from './LanguageFlag' | ||||
| import i18n from '../../../i18n.json' | ||||
| 
 | ||||
| export const Language: React.FC = () => { | ||||
| export interface LanguageProps { | ||||
|   className?: string | ||||
| } | ||||
| 
 | ||||
| export const Language: React.FC<LanguageProps> = (props) => { | ||||
|   const { className } = props | ||||
|   const { lang: currentLanguage } = useTranslation() | ||||
|   const [hiddenMenu, setHiddenMenu] = useState(true) | ||||
| 
 | ||||
| @@ -46,7 +51,8 @@ export const Language: React.FC = () => { | ||||
|       <ul | ||||
|         data-cy='languages-list' | ||||
|         className={classNames( | ||||
|           'absolute -bottom-16 z-10 mt-3 mr-4 flex w-24 list-none flex-col items-center justify-center rounded-lg bg-white p-0 shadow-light dark:bg-black dark:shadow-dark', | ||||
|           className, | ||||
|           'absolute top-16 z-10 mr-4 flex w-24 list-none flex-col items-center justify-center rounded-lg bg-white p-0 shadow-lightFlag dark:bg-black dark:shadow-darkFlag', | ||||
|           { hidden: hiddenMenu } | ||||
|         )} | ||||
|       > | ||||
| @@ -10,6 +10,7 @@ export const LanguageFlag: React.FC<LanguageFlagProps> = (props) => { | ||||
|   return ( | ||||
|     <> | ||||
|       <Image | ||||
|         quality={100} | ||||
|         width={35} | ||||
|         height={35} | ||||
|         src={`/images/svg/languages/${language}.svg`} | ||||
|   | ||||
							
								
								
									
										1
									
								
								components/Header/Language/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								components/Header/Language/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| export * from './Language' | ||||
| @@ -4,14 +4,14 @@ import useTranslation from 'next-translate/useTranslation' | ||||
| import { FetchState as FormStateType } from '../../../hooks/useFetchState' | ||||
| import { Loader } from '../Loader' | ||||
|  | ||||
| export interface FormStateProps { | ||||
| export interface FormStateProps extends React.ComponentPropsWithoutRef<'div'> { | ||||
|   state: FormStateType | ||||
|   message?: string | null | ||||
|   id?: string | ||||
| } | ||||
|  | ||||
| export const FormState: React.FC<FormStateProps> = (props) => { | ||||
|   const { state, message, id } = props | ||||
|   const { state, message, id, ...rest } = props | ||||
|   const { t } = useTranslation() | ||||
|  | ||||
|   if (state === 'loading') { | ||||
| @@ -29,25 +29,26 @@ export const FormState: React.FC<FormStateProps> = (props) => { | ||||
|   return ( | ||||
|     <> | ||||
|       <div | ||||
|         {...rest} | ||||
|         className={classNames( | ||||
|           'relative mt-6 flex max-w-xl flex-row items-center text-center font-medium', | ||||
|           props.className, | ||||
|           'mt-6 flex max-w-xl items-center text-center font-medium', | ||||
|           { | ||||
|             'text-red-800 dark:text-red-400': state === 'error', | ||||
|             'text-green-800 dark:text-green-400': state === 'success' | ||||
|           } | ||||
|         )} | ||||
|       > | ||||
|         <div className='thumbnail absolute top-0 inline-block bg-cover font-headline'></div> | ||||
|         <span id={id} className={classNames({ 'pl-6': state === 'error' })}> | ||||
|         <div className='thumbnail inline bg-cover font-headline' /> | ||||
|         <span id={id} className='pl-2'> | ||||
|           <b>{t(`errors:${state}`)}:</b> {message} | ||||
|         </span> | ||||
|       </div> | ||||
|  | ||||
|       <style jsx>{` | ||||
|         .thumbnail { | ||||
|           min-width: 20px; | ||||
|           height: 20px; | ||||
|           width: 20px; | ||||
|           height: ${state === 'error' ? '20px' : '25px'}; | ||||
|           background-image: url('/images/svg/icons/input/${state}.svg'); | ||||
|         } | ||||
|       `}</style> | ||||
|   | ||||
| @@ -2,7 +2,9 @@ import { useMemo } from 'react' | ||||
| import Image from 'next/image' | ||||
| import classNames from 'classnames' | ||||
|  | ||||
| export type SocialMedia = 'Discord' | 'GitHub' | 'Google' | ||||
| import { ProviderOAuth } from '../../../models/OAuth' | ||||
|  | ||||
| export type SocialMedia = ProviderOAuth | ||||
|  | ||||
| type SocialMediaColors = { | ||||
|   [key in SocialMedia]: string | ||||
| @@ -27,6 +29,7 @@ const SocialMediaChildren: React.FC<SocialMediaChildrenProps> = (props) => { | ||||
|   return ( | ||||
|     <> | ||||
|       <Image | ||||
|         quality={100} | ||||
|         width={20} | ||||
|         height={20} | ||||
|         src={`/images/svg/web/${socialMedia}.svg`} | ||||
|   | ||||
| @@ -86,6 +86,8 @@ export const useForm = (options: UseFormOptions): UseFormResult => { | ||||
|  | ||||
|   const handleSubmit: HandleSubmit = (callback) => { | ||||
|     return async (formData: ObjectAny, formElement) => { | ||||
|       setErrors({}) | ||||
|       setMessageTranslationKey(undefined) | ||||
|       if (replaceEmptyStringToNull) { | ||||
|         formData = replaceEmptyStringInObjectToNull( | ||||
|           formData, | ||||
|   | ||||
							
								
								
									
										20
									
								
								i18n.json
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								i18n.json
									
									
									
									
									
								
							| @@ -3,21 +3,21 @@ | ||||
|   "defaultLocale": "en", | ||||
|   "pages": { | ||||
|     "*": ["common"], | ||||
|     "/": ["home"], | ||||
|     "/404": ["errors"], | ||||
|     "/500": ["errors"], | ||||
|     "/authentication/forgot-password": ["authentication", "errors"], | ||||
|     "/authentication/reset-password": ["authentication", "errors"], | ||||
|     "/authentication/signup": ["authentication", "errors"], | ||||
|     "/authentication/signin": ["authentication", "errors"], | ||||
|     "/application/users/[userId]": ["application", "errors"], | ||||
|     "/application/users/settings": ["application", "authentication", "errors"], | ||||
|     "/application/guilds/create": ["application", "errors"], | ||||
|     "/application/guilds/join": ["application", "errors"], | ||||
|     "/": ["home"], | ||||
|     "/application": ["application", "errors"], | ||||
|     "/application/[guildId]/settings": ["application", "errors"], | ||||
|     "/application/[guildId]/[channelId]": ["application", "errors"], | ||||
|     "/application/[guildId]/[channelId]/settings": ["application", "errors"], | ||||
|     "/application/[guildId]/channels/create": ["application", "errors"] | ||||
|     "/application/[guildId]/channels/create": ["application", "errors"], | ||||
|     "/application/guilds/create": ["application", "errors"], | ||||
|     "/application/guilds/join": ["application", "errors"], | ||||
|     "/application/users/[userId]": ["application", "errors"], | ||||
|     "/application/users/settings": ["application", "authentication", "errors"], | ||||
|     "/authentication/forgot-password": ["authentication", "errors"], | ||||
|     "/authentication/reset-password": ["authentication", "errors"], | ||||
|     "/authentication/signin": ["authentication", "errors"], | ||||
|     "/authentication/signup": ["authentication", "errors"] | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,8 @@ | ||||
|   "label-checkbox-guilds": "Show the list of guilds to everyone.", | ||||
|   "nothing-here": "Nothing's here...", | ||||
|   "save": "Save", | ||||
|   "saved-information": "The information has been saved.", | ||||
|   "success-email-changed": "Please check your emails to confirm your new email address. You are now signed out.", | ||||
|   "delete": "Delete", | ||||
|   "signout": "Sign out", | ||||
|   "leave": "Leave", | ||||
|   | ||||
| @@ -15,6 +15,8 @@ | ||||
|   "label-checkbox-guilds": "Afficher la liste des guildes au public.", | ||||
|   "nothing-here": "Il n'y a rien ici...", | ||||
|   "save": "Sauvegarder", | ||||
|   "saved-information": "Les informations ont été enregistrées.", | ||||
|   "success-email-changed": "Veuillez vérifier vos emails pour confirmer votre nouvelle adresse email. Vous êtes maintenant déconnecté.", | ||||
|   "delete": "Supprimer", | ||||
|   "signout": "Se déconnecter", | ||||
|   "leave": "Quitter", | ||||
|   | ||||
| @@ -2,8 +2,8 @@ import { Type } from '@sinclair/typebox' | ||||
|  | ||||
| import { date, id } from './utils' | ||||
|  | ||||
| export const providers = ['google', 'github', 'discord'] as const | ||||
| export const strategies = [...providers, 'local'] as const | ||||
| export const providers = ['Google', 'GitHub', 'Discord'] as const | ||||
| export const strategies = [...providers, 'Local'] as const | ||||
|  | ||||
| export const strategiesTypebox = strategies.map((strategy) => | ||||
|   Type.Literal(strategy) | ||||
|   | ||||
| @@ -25,7 +25,7 @@ export const userSchema = { | ||||
|     Type.String({ | ||||
|       minLength: 1, | ||||
|       maxLength: 255, | ||||
|       format: 'uri-reference' | ||||
|       format: 'uri' | ||||
|     }), | ||||
|     Type.Null() | ||||
|   ]), | ||||
|   | ||||
| @@ -8,6 +8,7 @@ module.exports = nextTranslate( | ||||
|     images: { | ||||
|       domains: [ | ||||
|         'api.thream.divlo.fr', | ||||
|         'thream-api.herokuapp.com', | ||||
|         ...(process.env.NODE_ENV !== 'production' ? ['localhost'] : []) | ||||
|       ] | ||||
|     }, | ||||
|   | ||||
							
								
								
									
										4482
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4482
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										50
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								package.json
									
									
									
									
									
								
							| @@ -32,29 +32,29 @@ | ||||
|     "postinstall": "husky install" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@fontsource/montserrat": "4.5.5", | ||||
|     "@fontsource/roboto": "4.5.3", | ||||
|     "@fontsource/montserrat": "4.5.7", | ||||
|     "@fontsource/roboto": "4.5.5", | ||||
|     "@heroicons/react": "1.0.6", | ||||
|     "@sinclair/typebox": "0.23.4", | ||||
|     "ajv": "8.10.0", | ||||
|     "ajv": "8.11.0", | ||||
|     "ajv-formats": "2.1.1", | ||||
|     "axios": "0.26.1", | ||||
|     "classnames": "2.3.1", | ||||
|     "date-and-time": "2.3.0", | ||||
|     "emoji-mart": "3.0.1", | ||||
|     "katex": "0.15.3", | ||||
|     "next": "12.1.0", | ||||
|     "next-pwa": "5.4.6", | ||||
|     "next": "12.1.4", | ||||
|     "next-pwa": "5.4.7", | ||||
|     "next-themes": "0.1.1", | ||||
|     "next-translate": "1.3.5", | ||||
|     "pretty-bytes": "6.0.0", | ||||
|     "react": "17.0.2", | ||||
|     "react-component-form": "2.0.0", | ||||
|     "react-component-form": "3.0.1", | ||||
|     "react-dom": "17.0.2", | ||||
|     "react-infinite-scroll-component": "6.1.0", | ||||
|     "react-markdown": "8.0.1", | ||||
|     "react-markdown": "8.0.2", | ||||
|     "react-responsive": "8.2.0", | ||||
|     "react-swipeable": "6.2.0", | ||||
|     "react-swipeable": "6.2.1", | ||||
|     "react-textarea-autosize": "8.3.3", | ||||
|     "read-pkg": "7.1.0", | ||||
|     "rehype-katex": "6.0.2", | ||||
| @@ -72,45 +72,45 @@ | ||||
|     "@commitlint/config-conventional": "16.2.1", | ||||
|     "@lhci/cli": "0.9.0", | ||||
|     "@saithodev/semantic-release-backmerge": "2.1.2", | ||||
|     "@testing-library/jest-dom": "5.16.2", | ||||
|     "@testing-library/jest-dom": "5.16.4", | ||||
|     "@testing-library/react": "12.1.4", | ||||
|     "@types/emoji-mart": "3.0.9", | ||||
|     "@types/hast": "2.3.4", | ||||
|     "@types/jest": "27.4.1", | ||||
|     "@types/katex": "0.11.1", | ||||
|     "@types/node": "17.0.21", | ||||
|     "@types/react": "17.0.40", | ||||
|     "@types/katex": "0.14.0", | ||||
|     "@types/node": "17.0.23", | ||||
|     "@types/react": "17.0.43", | ||||
|     "@types/react-responsive": "8.0.5", | ||||
|     "@types/unist": "2.0.6", | ||||
|     "@typescript-eslint/eslint-plugin": "5.15.0", | ||||
|     "@typescript-eslint/parser": "5.15.0", | ||||
|     "@typescript-eslint/eslint-plugin": "5.18.0", | ||||
|     "@typescript-eslint/parser": "5.18.0", | ||||
|     "autoprefixer": "10.4.4", | ||||
|     "cypress": "9.5.2", | ||||
|     "cypress": "9.5.3", | ||||
|     "editorconfig-checker": "4.0.2", | ||||
|     "eslint": "8.11.0", | ||||
|     "eslint-config-conventions": "1.1.1", | ||||
|     "eslint-config-next": "12.1.0", | ||||
|     "eslint": "8.12.0", | ||||
|     "eslint-config-conventions": "2.0.0", | ||||
|     "eslint-config-next": "12.1.4", | ||||
|     "eslint-config-prettier": "8.5.0", | ||||
|     "eslint-plugin-import": "2.25.4", | ||||
|     "eslint-plugin-import": "2.26.0", | ||||
|     "eslint-plugin-prettier": "4.0.0", | ||||
|     "eslint-plugin-promise": "6.0.0", | ||||
|     "eslint-plugin-unicorn": "41.0.0", | ||||
|     "html-w3c-validator": "1.1.0", | ||||
|     "eslint-plugin-unicorn": "42.0.0", | ||||
|     "html-w3c-validator": "1.2.0", | ||||
|     "husky": "7.0.4", | ||||
|     "jest": "27.5.1", | ||||
|     "lint-staged": "12.3.6", | ||||
|     "lint-staged": "12.3.7", | ||||
|     "markdownlint-cli": "0.31.1", | ||||
|     "mockttp": "2.7.0", | ||||
|     "next-secure-headers": "2.2.0", | ||||
|     "plop": "3.0.5", | ||||
|     "postcss": "8.4.12", | ||||
|     "prettier": "2.6.0", | ||||
|     "prettier": "2.6.2", | ||||
|     "prettier-plugin-tailwindcss": "0.1.8", | ||||
|     "semantic-release": "19.0.2", | ||||
|     "serve": "13.0.2", | ||||
|     "start-server-and-test": "1.14.0", | ||||
|     "tailwindcss": "3.0.23", | ||||
|     "typescript": "4.6.2", | ||||
|     "vercel": "24.0.0" | ||||
|     "typescript": "4.6.3", | ||||
|     "vercel": "24.0.1" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -20,6 +20,14 @@ const Application = ({ Component, pageProps }: AppProps): JSX.Element => { | ||||
|  | ||||
|   useEffect(() => { | ||||
|     cookies.set('NEXT_LOCALE', lang) | ||||
|     const appHeight = (): void => { | ||||
|       document.documentElement.style.setProperty( | ||||
|         '--app-height', | ||||
|         `${window.innerHeight}px` | ||||
|       ) | ||||
|     } | ||||
|     window.addEventListener('resize', appHeight) | ||||
|     appHeight() | ||||
|   }, [lang]) | ||||
|  | ||||
|   return ( | ||||
|   | ||||
| @@ -26,6 +26,7 @@ const Home: NextPage<FooterProps> = (props) => { | ||||
|             <Link href='/authentication/signup'> | ||||
|               <a> | ||||
|                 <Image | ||||
|                   quality={100} | ||||
|                   width={351} | ||||
|                   height={341} | ||||
|                   src='/images/svg/design/home.svg' | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 22 22"> | ||||
|     <g fill="none" fill-rule="evenodd" transform="matrix(1 0 0 -1 0 22)"> | ||||
|         <path fill="#F95555" fill-rule="nonzero" d="M11 0C4.925 0 0 4.925 0 11s4.925 11 11 11 11-4.925 11-11S17.075 0 11 0z"/> | ||||
|         <path fill="#FFF" fill-rule="nonzero" stroke="#FFF" stroke-width=".5" d="M9.875 7.063c0 .62.504 1.125 1.125 1.125s1.125-.504 1.125-1.126c0-.62-.504-1.125-1.125-1.125s-1.125.504-1.125 1.125z"/> | ||||
|         <rect width="2.25" height="6.75" x="9.875" y="9.875" fill="#FFF" rx="1"/> | ||||
|     </g> | ||||
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 22 22"> | ||||
|   <g fill="none" fill-rule="evenodd" transform="matrix(1 0 0 -1 0 22)"> | ||||
|     <path fill="#F95555" fill-rule="nonzero" d="M11 0C4.925 0 0 4.925 0 11s4.925 11 11 11 11-4.925 11-11S17.075 0 11 0z" /> | ||||
|     <path fill="#FFF" fill-rule="nonzero" stroke="#FFF" stroke-width=".5" d="M9.875 7.063c0 .62.504 1.125 1.125 1.125s1.125-.504 1.125-1.126c0-.62-.504-1.125-1.125-1.125s-1.125.504-1.125 1.125z" /> | ||||
|     <rect width="2.25" height="6.75" x="9.875" y="9.875" fill="#FFF" rx="1" /> | ||||
|   </g> | ||||
| </svg> | ||||
|   | ||||
| Before Width: | Height: | Size: 584 B After Width: | Height: | Size: 571 B | 
| @@ -1,3 +1,4 @@ | ||||
| <?xml version="1.0" ?><svg style="enable-background:new 0 0 612 792;" version="1.1" viewBox="0 0 612 792" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><style type="text/css"> | ||||
| 	.st0{fill:#41AD49;} | ||||
| </style><g><path class="st0" d="M562,396c0-141.4-114.6-256-256-256S50,254.6,50,396s114.6,256,256,256S562,537.4,562,396L562,396z    M501.7,296.3l-241,241l0,0l-17.2,17.2L110.3,421.3l58.8-58.8l74.5,74.5l199.4-199.4L501.7,296.3L501.7,296.3z"/></g></svg> | ||||
| <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" width="20" height="20" preserveAspectRatio="xMidYMid meet" viewBox="0 0 36 36"> | ||||
|   <path fill="#41ad49" d="M18 2a16 16 0 1 0 16 16A16 16 0 0 0 18 2Zm10.45 10.63L15.31 25.76L7.55 18a1.4 1.4 0 0 1 2-2l5.78 5.78l11.14-11.13a1.4 1.4 0 1 1 2 2Z" class="clr-i-solid clr-i-solid-path-1" /> | ||||
|   <path fill="none" d="M0 0h36v36H0z" /> | ||||
| </svg> | ||||
|   | ||||
| Before Width: | Height: | Size: 500 B After Width: | Height: | Size: 443 B | 
| @@ -2,6 +2,12 @@ | ||||
| @tailwind components; | ||||
| @tailwind utilities; | ||||
|  | ||||
| :root { | ||||
|   --app-height: 100vh; | ||||
|   --scroll-bar-color: #c5c5c5; | ||||
|   --scroll-bar-bg-color: transparent; | ||||
| } | ||||
|  | ||||
| #__next { | ||||
|   @apply flex h-screen flex-col; | ||||
| } | ||||
| @@ -12,12 +18,7 @@ body { | ||||
| } | ||||
|  | ||||
| .h-full-without-header { | ||||
|   height: calc(100vh - 64px); | ||||
| } | ||||
|  | ||||
| :root { | ||||
|   --scroll-bar-color: #c5c5c5; | ||||
|   --scroll-bar-bg-color: transparent; | ||||
|   height: calc(var(--app-height) - 64px); | ||||
| } | ||||
|  | ||||
| ::-webkit-scrollbar { | ||||
|   | ||||
| @@ -17,6 +17,8 @@ module.exports = { | ||||
|       boxShadow: { | ||||
|         dark: '0px 1px 10px hsla(0, 0%, 100%, 0.2)', | ||||
|         light: '0px 1px 10px rgba(0, 0, 0, 0.25)', | ||||
|         darkFlag: '0px 1px 10px hsla(0, 0%, 100%, 0.2)', | ||||
|         lightFlag: '0px 1px 10px rgba(0, 0, 0, 0.25)', | ||||
|         green: '0 0 0 2px #27b05e', | ||||
|         success: '0 2px 8px rgba(70, 200, 92, 0.3)', | ||||
|         error: '0 2px 8px rgba(200, 69, 69, 0.5)' | ||||
|   | ||||
		Reference in New Issue
	
	Block a user