fix(i18n): load locales from file system, instead of http-backend
@ -1,8 +0,0 @@
|
||||
NEXT_PUBLIC_APP_VERSION=$npm_package_version
|
||||
|
||||
# App & Server URLs
|
||||
NEXT_PUBLIC_APP_URL=$APP_URL
|
||||
NEXT_PUBLIC_SERVER_URL=$SERVER_URL
|
||||
|
||||
# Google OAuth
|
||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID
|
||||
@ -19,5 +19,8 @@
|
||||
],
|
||||
"env": {
|
||||
"jest": true
|
||||
},
|
||||
"rules": {
|
||||
"@next/next/no-img-element": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
} from '@mui/icons-material';
|
||||
import { ButtonBase, Divider, Tooltip } from '@mui/material';
|
||||
import { ButtonBase, Divider, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
||||
import clsx from 'clsx';
|
||||
import { get } from 'lodash';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@ -28,9 +28,11 @@ import styles from './ArtboardController.module.scss';
|
||||
const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, centerView }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const theme = useTheme();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const resume = useAppSelector((state) => state.resume);
|
||||
const isDesktop = useMediaQuery(theme.breakpoints.up('sm'));
|
||||
const { left, right } = useAppSelector((state) => state.build.sidebar);
|
||||
const orientation = useAppSelector((state) => state.build.page.orientation);
|
||||
|
||||
@ -92,29 +94,33 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
|
||||
|
||||
<Divider />
|
||||
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-orientation')}>
|
||||
<ButtonBase onClick={handleTogglePageOrientation}>
|
||||
{orientation === 'vertical' ? (
|
||||
<AlignHorizontalCenter fontSize="medium" />
|
||||
) : (
|
||||
<AlignVerticalCenter fontSize="medium" />
|
||||
)}
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
{isDesktop && (
|
||||
<>
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-orientation')}>
|
||||
<ButtonBase onClick={handleTogglePageOrientation}>
|
||||
{orientation === 'vertical' ? (
|
||||
<AlignHorizontalCenter fontSize="medium" />
|
||||
) : (
|
||||
<AlignVerticalCenter fontSize="medium" />
|
||||
)}
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-page-break-line')}>
|
||||
<ButtonBase onClick={handleTogglePageBreakLine}>
|
||||
<InsertPageBreak fontSize="medium" />
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-page-break-line')}>
|
||||
<ButtonBase onClick={handleTogglePageBreakLine}>
|
||||
<InsertPageBreak fontSize="medium" />
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-sidebars')}>
|
||||
<ButtonBase onClick={handleToggleSidebar}>
|
||||
<ViewSidebar fontSize="medium" />
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-sidebars')}>
|
||||
<ButtonBase onClick={handleToggleSidebar}>
|
||||
<ViewSidebar fontSize="medium" />
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
|
||||
<Divider />
|
||||
<Divider />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.copy-link')}>
|
||||
<ButtonBase onClick={handleCopyLink}>
|
||||
|
||||
@ -72,7 +72,7 @@ const RightSidebar = () => {
|
||||
<footer className={styles.footer}>
|
||||
<Footer />
|
||||
|
||||
<div>v{process.env.NEXT_PUBLIC_APP_VERSION}</div>
|
||||
<div>v{process.env.appVersion}</div>
|
||||
</footer>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@ -1,18 +1,15 @@
|
||||
import HttpBackend from 'i18next-http-backend';
|
||||
import { UserConfig } from 'next-i18next';
|
||||
import { join } from 'path';
|
||||
|
||||
const i18nConfig = {
|
||||
const i18nConfig: UserConfig = {
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en'],
|
||||
},
|
||||
debug: false,
|
||||
nsSeparator: '.',
|
||||
localePath: join(__dirname, '../../../public/locales'),
|
||||
ns: ['common', 'modals', 'landing', 'dashboard', 'builder'],
|
||||
serializeConfig: false,
|
||||
use: [HttpBackend],
|
||||
backend: {
|
||||
loadPath: `${process.env.NEXT_PUBLIC_APP_URL}/locales/{{lng}}/{{ns}}.json`,
|
||||
},
|
||||
};
|
||||
|
||||
export default i18nConfig;
|
||||
|
||||
@ -54,7 +54,7 @@ const LoginModal: React.FC = () => {
|
||||
);
|
||||
|
||||
const { signIn } = useGoogleLogin({
|
||||
clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
|
||||
clientId: process.env.googleClientId,
|
||||
onSuccess: async (response: GoogleLoginResponse) => {
|
||||
await loginWithGoogleMutation({ accessToken: response.accessToken });
|
||||
|
||||
|
||||
@ -9,12 +9,19 @@ const nextConfig = {
|
||||
locales: ['en'],
|
||||
},
|
||||
|
||||
env: {
|
||||
appUrl: process.env.APP_URL,
|
||||
serverUrl: process.env.SERVER_URL,
|
||||
appVersion: process.env.npm_package_version,
|
||||
googleClientId: process.env.GOOGLE_CLIENT_ID,
|
||||
},
|
||||
|
||||
nx: {
|
||||
svgr: false,
|
||||
},
|
||||
|
||||
images: {
|
||||
domains: ['localhost', 'www.gravatar.com'],
|
||||
domains: ['www.gravatar.com'],
|
||||
},
|
||||
|
||||
// Hack to make Tailwind darkMode 'class' strategy with CSS Modules
|
||||
|
||||
@ -20,8 +20,7 @@
|
||||
"options": {
|
||||
"buildTarget": "client:build",
|
||||
"dev": true,
|
||||
"port": 3000,
|
||||
"proxyConfig": "apps/client/proxy.conf.json"
|
||||
"port": 3000
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
|
||||
BIN
apps/client/public/images/covers/cover-0ee139.jpeg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
apps/client/public/images/covers/cover-1ab08.jpeg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
apps/client/public/images/covers/cover-1f8c9.jpeg
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
apps/client/public/images/covers/cover-1fe54f.jpeg
Normal file
|
After Width: | Height: | Size: 265 KiB |
BIN
apps/client/public/images/covers/cover-253f4a.jpeg
Normal file
|
After Width: | Height: | Size: 215 KiB |
BIN
apps/client/public/images/covers/cover-33aec.jpeg
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
apps/client/public/images/covers/cover-3sc.jpeg
Normal file
|
After Width: | Height: | Size: 174 KiB |
BIN
apps/client/public/images/covers/cover-466cb.jpeg
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
apps/client/public/images/covers/cover-478b3.jpeg
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
apps/client/public/images/covers/cover-4d9.jpeg
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
apps/client/public/images/covers/cover-4ed.jpeg
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
apps/client/public/images/covers/cover-4fd88.jpeg
Normal file
|
After Width: | Height: | Size: 274 KiB |
BIN
apps/client/public/images/covers/cover-50f3f3.jpeg
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
apps/client/public/images/covers/cover-6b8ae.jpeg
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
apps/client/public/images/covers/cover-6fa09.jpeg
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
apps/client/public/images/covers/cover-713b2f.jpeg
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
apps/client/public/images/covers/cover-737f2.jpeg
Normal file
|
After Width: | Height: | Size: 314 KiB |
BIN
apps/client/public/images/covers/cover-73dab8.jpeg
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
apps/client/public/images/covers/cover-79df42.jpeg
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
apps/client/public/images/covers/cover-7b601.jpeg
Normal file
|
After Width: | Height: | Size: 168 KiB |
BIN
apps/client/public/images/covers/cover-7dh.jpeg
Normal file
|
After Width: | Height: | Size: 319 KiB |
BIN
apps/client/public/images/covers/cover-7e6ae.jpeg
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
apps/client/public/images/covers/cover-94b.jpeg
Normal file
|
After Width: | Height: | Size: 329 KiB |
BIN
apps/client/public/images/covers/cover-96bdd.jpeg
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
apps/client/public/images/covers/cover-98afd.jpeg
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
apps/client/public/images/covers/cover-9hk.jpeg
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
apps/client/public/images/covers/cover-b26e75.jpeg
Normal file
|
After Width: | Height: | Size: 323 KiB |
BIN
apps/client/public/images/covers/cover-b6ea6.jpeg
Normal file
|
After Width: | Height: | Size: 267 KiB |
BIN
apps/client/public/images/covers/cover-c219f2.jpeg
Normal file
|
After Width: | Height: | Size: 253 KiB |
BIN
apps/client/public/images/covers/cover-c3642.jpeg
Normal file
|
After Width: | Height: | Size: 255 KiB |
BIN
apps/client/public/images/covers/cover-c584b.jpeg
Normal file
|
After Width: | Height: | Size: 272 KiB |
BIN
apps/client/public/images/covers/cover-c682cb.jpeg
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
apps/client/public/images/covers/cover-c82a8.jpeg
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
apps/client/public/images/covers/cover-d312a7.jpeg
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
apps/client/public/images/covers/cover-dcbd8.jpeg
Normal file
|
After Width: | Height: | Size: 279 KiB |
BIN
apps/client/public/images/covers/cover-df274.jpeg
Normal file
|
After Width: | Height: | Size: 165 KiB |
BIN
apps/client/public/images/covers/cover-e26ee.jpeg
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
apps/client/public/images/covers/cover-f3034.jpeg
Normal file
|
After Width: | Height: | Size: 382 KiB |
BIN
apps/client/public/images/covers/cover-fec87.jpeg
Normal file
|
After Width: | Height: | Size: 205 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 148 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
@ -13,11 +13,11 @@ export type ServerError = {
|
||||
};
|
||||
|
||||
const axios = _axios.create({
|
||||
baseURL: `${process.env.NEXT_PUBLIC_SERVER_URL}/api`,
|
||||
baseURL: `${process.env.serverUrl}/api`,
|
||||
});
|
||||
|
||||
export const uninterceptedAxios = _axios.create({
|
||||
baseURL: `${process.env.NEXT_PUBLIC_SERVER_URL}/api`,
|
||||
baseURL: `${process.env.serverUrl}/api`,
|
||||
});
|
||||
|
||||
axios.interceptors.request.use((config) => {
|
||||
|
||||
@ -4,7 +4,6 @@ import { Theme } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import Image from 'next/image';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import Markdown from '@/components/shared/Markdown';
|
||||
@ -25,10 +24,9 @@ export const MastheadSidebar: React.FC = () => {
|
||||
return (
|
||||
<div className="col-span-2 grid justify-items-start gap-3 px-4 pt-4">
|
||||
{photo.visible && !isEmpty(photo.url) && (
|
||||
<Image
|
||||
<img
|
||||
alt={name}
|
||||
src={photo.url}
|
||||
objectFit="cover"
|
||||
width={photo.filters.size}
|
||||
height={photo.filters.size}
|
||||
className={getPhotoClassNames(photo.filters)}
|
||||
|
||||
@ -5,7 +5,6 @@ import { Theme } from '@reactive-resume/schema';
|
||||
import clsx from 'clsx';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import Image from 'next/image';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import Markdown from '@/components/shared/Markdown';
|
||||
@ -26,10 +25,9 @@ export const MastheadSidebar: React.FC = () => {
|
||||
return (
|
||||
<div className="col-span-2 grid justify-items-start gap-3 p-4">
|
||||
{photo.visible && !isEmpty(photo.url) && (
|
||||
<Image
|
||||
<img
|
||||
alt={name}
|
||||
src={photo.url}
|
||||
objectFit="cover"
|
||||
width={photo.filters.size}
|
||||
height={photo.filters.size}
|
||||
className={getPhotoClassNames(photo.filters)}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { Email, Phone, Public, Room } from '@mui/icons-material';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import Image from 'next/image';
|
||||
|
||||
import Markdown from '@/components/shared/Markdown';
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
@ -18,19 +17,18 @@ export const MastheadSidebar: React.FC = () => {
|
||||
return (
|
||||
<div className="col-span-2 grid justify-items-center gap-4">
|
||||
{photo.visible && !isEmpty(photo.url) && (
|
||||
<Image
|
||||
<img
|
||||
alt={name}
|
||||
src={photo.url}
|
||||
objectFit="cover"
|
||||
width={photo.filters.size}
|
||||
height={photo.filters.size}
|
||||
className={getPhotoClassNames(photo.filters)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<div className="text-center">
|
||||
<h1>{name}</h1>
|
||||
<p className="opacity-75">{headline}</p>
|
||||
<p className="mt-1 opacity-75">{headline}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 rounded border-2 p-4" style={{ borderColor: primaryColor }}>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Email, Phone, Public, Room } from '@mui/icons-material';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import Image from 'next/image';
|
||||
import React from 'react';
|
||||
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
@ -14,22 +13,25 @@ const Masthead = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mb-4 border-b pb-4 text-center">
|
||||
{photo.visible && !isEmpty(photo.url) && (
|
||||
<Image
|
||||
alt={name}
|
||||
src={photo.url}
|
||||
width={photo.filters.size}
|
||||
height={photo.filters.size}
|
||||
objectFit="cover"
|
||||
className={getPhotoClassNames(photo.filters)}
|
||||
/>
|
||||
)}
|
||||
<div className="grid gap-3 justify-center mb-4 border-b pb-4 text-center">
|
||||
<div className="mx-auto">
|
||||
{photo.visible && !isEmpty(photo.url) && (
|
||||
<img
|
||||
alt={name}
|
||||
src={photo.url}
|
||||
width={photo.filters.size}
|
||||
height={photo.filters.size}
|
||||
className={getPhotoClassNames(photo.filters)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h1 className="mt-2 mb-1">{name}</h1>
|
||||
<p className="opacity-75">{headline}</p>
|
||||
<div>
|
||||
<h1 className="mb-1">{name}</h1>
|
||||
<p className="opacity-75">{headline}</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-wrap justify-center gap-3">
|
||||
<div className="flex flex-wrap justify-center gap-3">
|
||||
<DataDisplay icon={<Email />} link={`mailto:${email}`}>
|
||||
{email}
|
||||
</DataDisplay>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Email, Phone, Public, Room } from '@mui/icons-material';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { useAppSelector } from '@/store/hooks';
|
||||
import DataDisplay from '@/templates/shared/DataDisplay';
|
||||
@ -15,12 +14,11 @@ const Masthead: React.FC = () => {
|
||||
return (
|
||||
<div className="flex items-center gap-4">
|
||||
{photo.visible && !isEmpty(photo.url) && (
|
||||
<Image
|
||||
<img
|
||||
alt={name}
|
||||
src={photo.url}
|
||||
width={photo.filters.size}
|
||||
height={photo.filters.size}
|
||||
objectFit="cover"
|
||||
className={getPhotoClassNames(photo.filters)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -2,7 +2,6 @@ import { Email, Phone, Public, Room } from '@mui/icons-material';
|
||||
import { Theme } from '@reactive-resume/schema';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import Image from 'next/image';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import Markdown from '@/components/shared/Markdown';
|
||||
@ -19,13 +18,7 @@ export const MastheadSidebar: React.FC = () => {
|
||||
<div className="col-span-2 grid justify-items-center gap-4">
|
||||
{photo.visible && !isEmpty(photo.url) && (
|
||||
<div className="relative aspect-square h-full w-full">
|
||||
<Image
|
||||
alt={name}
|
||||
layout="fill"
|
||||
src={photo.url}
|
||||
objectFit="cover"
|
||||
className={getPhotoClassNames(photo.filters)}
|
||||
/>
|
||||
<img alt={name} src={photo.url} className={getPhotoClassNames(photo.filters)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@ -18,37 +18,37 @@ const templateMap: Record<string, TemplateMeta> = {
|
||||
kakuna: {
|
||||
id: 'kakuna',
|
||||
name: 'Kakuna',
|
||||
preview: require('./Kakuna/preview.jpg'),
|
||||
preview: '/images/templates/kakuna.jpg',
|
||||
component: Kakuna,
|
||||
},
|
||||
onyx: {
|
||||
id: 'onyx',
|
||||
name: 'Onyx',
|
||||
preview: require('./Onyx/preview.jpg'),
|
||||
preview: '/images/templates/onyx.jpg',
|
||||
component: Onyx,
|
||||
},
|
||||
pikachu: {
|
||||
id: 'pikachu',
|
||||
name: 'Pikachu',
|
||||
preview: require('./Pikachu/preview.jpg'),
|
||||
preview: '/images/templates/pikachu.jpg',
|
||||
component: Pikachu,
|
||||
},
|
||||
gengar: {
|
||||
id: 'gengar',
|
||||
name: 'Gengar',
|
||||
preview: require('./Gengar/preview.jpg'),
|
||||
preview: '/images/templates/gengar.jpg',
|
||||
component: Gengar,
|
||||
},
|
||||
castform: {
|
||||
id: 'castform',
|
||||
name: 'Castform',
|
||||
preview: require('./Castform/preview.jpg'),
|
||||
preview: '/images/templates/castform.jpg',
|
||||
component: Castform,
|
||||
},
|
||||
glalie: {
|
||||
id: 'glalie',
|
||||
name: 'Glalie',
|
||||
preview: require('./Glalie/preview.jpg'),
|
||||
preview: '/images/templates/glalie.jpg',
|
||||
component: Glalie,
|
||||
},
|
||||
};
|
||||
|
||||
5
apps/client/types/environment.d.ts
vendored
@ -2,12 +2,7 @@ declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
TZ: string;
|
||||
ANALYZE?: boolean;
|
||||
NODE_ENV: 'development' | 'production';
|
||||
|
||||
// Public Environment Variables
|
||||
NEXT_PUBLIC_APP_VERSION?: string;
|
||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +51,7 @@ export const parseListItemPath = (item: ListItem, path: string | string[], separ
|
||||
|
||||
export const getPhotoClassNames = (filters: PhotoFilters) =>
|
||||
clsx({
|
||||
'object-cover': true,
|
||||
grayscale: filters.grayscale,
|
||||
'!border-[4px] !border-solid': filters.border,
|
||||
'rounded-lg': filters.shape === 'rounded-square',
|
||||
|
||||