fix: verify view is inaccessible (#8557)
Fixes https://github.com/payloadcms/payload/issues/8470 Cleans up the way we redirect and where it happens. ## Improvements - When you verify, the admin panel will display a toast when it redirects you to the login route. This is contextually helpful as to what is happening. - Removes dead code path, as we always set the _verifiedToken to null after it is used. ## `handleAdminPage` renamed to `getRouteInfo` This function no longer handles routing. It kicks that responsibility back up to the initPage function. ## `isAdminAuthRoute` renamed to `isPublicAdminRoute` This was inversely named as it determines if a given route is public. Also simplifies deterministic logic here. ## `redirectUnauthenticatedUser` argument This is no longer used or needed. We can determine these things by using the `isPublicAdminRoute` function. ## View Style fixes - Reset Password - Forgot Password - Unauthorized
This commit is contained in:
@@ -19,8 +19,8 @@ export function login(collection: Collection): any {
|
||||
|
||||
const result = await loginOperation(options)
|
||||
const cookie = generatePayloadCookie({
|
||||
collectionConfig: collection.config,
|
||||
payload: context.req.payload,
|
||||
collectionAuthConfig: collection.config.auth,
|
||||
cookiePrefix: context.req.payload.config.cookiePrefix,
|
||||
token: result.token,
|
||||
})
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ export function logout(collection: Collection): any {
|
||||
|
||||
const result = await logoutOperation(options)
|
||||
const expiredCookie = generateExpiredPayloadCookie({
|
||||
collectionConfig: collection.config,
|
||||
payload: context.req.payload,
|
||||
collectionAuthConfig: collection.config.auth,
|
||||
config: context.req.payload.config,
|
||||
cookiePrefix: context.req.payload.config.cookiePrefix,
|
||||
})
|
||||
context.headers['Set-Cookie'] = expiredCookie
|
||||
return result
|
||||
|
||||
@@ -14,8 +14,8 @@ export function refresh(collection: Collection): any {
|
||||
|
||||
const result = await refreshOperation(options)
|
||||
const cookie = generatePayloadCookie({
|
||||
collectionConfig: collection.config,
|
||||
payload: context.req.payload,
|
||||
collectionAuthConfig: collection.config.auth,
|
||||
cookiePrefix: context.req.payload.config.cookiePrefix,
|
||||
token: result.refreshedToken,
|
||||
})
|
||||
context.headers['Set-Cookie'] = cookie
|
||||
|
||||
@@ -23,8 +23,8 @@ export function resetPassword(collection: Collection): any {
|
||||
|
||||
const result = await resetPasswordOperation(options)
|
||||
const cookie = generatePayloadCookie({
|
||||
collectionConfig: collection.config,
|
||||
payload: context.req.payload,
|
||||
collectionAuthConfig: collection.config.auth,
|
||||
cookiePrefix: context.req.payload.config.cookiePrefix,
|
||||
token: result.token,
|
||||
})
|
||||
context.headers['Set-Cookie'] = cookie
|
||||
|
||||
6
packages/next/src/elements/FormHeader/index.scss
Normal file
6
packages/next/src/elements/FormHeader/index.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
.form-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(var(--base) * .5);
|
||||
margin-bottom: var(--base);
|
||||
}
|
||||
22
packages/next/src/elements/FormHeader/index.tsx
Normal file
22
packages/next/src/elements/FormHeader/index.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
const baseClass = 'form-header'
|
||||
|
||||
type Props = {
|
||||
description?: React.ReactNode | string
|
||||
heading: string
|
||||
}
|
||||
export function FormHeader({ description, heading }: Props) {
|
||||
if (!heading) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<h1>{heading}</h1>
|
||||
{Boolean(description) && <p>{description}</p>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -29,8 +29,8 @@ export const login: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
})
|
||||
|
||||
const cookie = generatePayloadCookie({
|
||||
collectionConfig: collection.config,
|
||||
payload: req.payload,
|
||||
collectionAuthConfig: collection.config.auth,
|
||||
cookiePrefix: req.payload.config.cookiePrefix,
|
||||
token: result.token,
|
||||
})
|
||||
|
||||
|
||||
@@ -30,8 +30,9 @@ export const logout: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
}
|
||||
|
||||
const expiredCookie = generateExpiredPayloadCookie({
|
||||
collectionConfig: collection.config,
|
||||
payload: req.payload,
|
||||
collectionAuthConfig: collection.config.auth,
|
||||
config: req.payload.config,
|
||||
cookiePrefix: req.payload.config.cookiePrefix,
|
||||
})
|
||||
|
||||
headers.set('Set-Cookie', expiredCookie)
|
||||
|
||||
@@ -20,8 +20,8 @@ export const refresh: CollectionRouteHandler = async ({ collection, req }) => {
|
||||
|
||||
if (result.setCookie) {
|
||||
const cookie = generatePayloadCookie({
|
||||
collectionConfig: collection.config,
|
||||
payload: req.payload,
|
||||
collectionAuthConfig: collection.config.auth,
|
||||
cookiePrefix: req.payload.config.cookiePrefix,
|
||||
token: result.refreshedToken,
|
||||
})
|
||||
|
||||
|
||||
@@ -28,8 +28,8 @@ export const registerFirstUser: CollectionRouteHandler = async ({ collection, re
|
||||
})
|
||||
|
||||
const cookie = generatePayloadCookie({
|
||||
collectionConfig: collection.config,
|
||||
payload: req.payload,
|
||||
collectionAuthConfig: collection.config.auth,
|
||||
cookiePrefix: req.payload.config.cookiePrefix,
|
||||
token: result.token,
|
||||
})
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ export const resetPassword: CollectionRouteHandler = async ({ collection, req })
|
||||
})
|
||||
|
||||
const cookie = generatePayloadCookie({
|
||||
collectionConfig: collection.config,
|
||||
payload: req.payload,
|
||||
collectionAuthConfig: collection.config.auth,
|
||||
cookiePrefix: req.payload.config.cookiePrefix,
|
||||
token: result.token,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
import type {
|
||||
Permissions,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedConfig,
|
||||
SanitizedGlobalConfig,
|
||||
} from 'payload'
|
||||
import type { SanitizedCollectionConfig, SanitizedConfig, SanitizedGlobalConfig } from 'payload'
|
||||
|
||||
import { notFound } from 'next/navigation.js'
|
||||
import { getRouteWithoutAdmin, isAdminRoute } from './shared.js'
|
||||
|
||||
import { getRouteWithoutAdmin, isAdminAuthRoute, isAdminRoute } from './shared.js'
|
||||
|
||||
export const handleAdminPage = ({
|
||||
adminRoute,
|
||||
config,
|
||||
permissions,
|
||||
route,
|
||||
}: {
|
||||
type Args = {
|
||||
adminRoute: string
|
||||
config: SanitizedConfig
|
||||
permissions: Permissions
|
||||
route: string
|
||||
}) => {
|
||||
}
|
||||
type RouteInfo = {
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
collectionSlug?: string
|
||||
docID?: string
|
||||
globalConfig?: SanitizedGlobalConfig
|
||||
globalSlug?: string
|
||||
}
|
||||
|
||||
export function getRouteInfo({ adminRoute, config, route }: Args): RouteInfo {
|
||||
if (isAdminRoute({ adminRoute, config, route })) {
|
||||
const routeWithoutAdmin = getRouteWithoutAdmin({ adminRoute, route })
|
||||
const routeSegments = routeWithoutAdmin.split('/').filter(Boolean)
|
||||
@@ -33,28 +29,18 @@ export const handleAdminPage = ({
|
||||
|
||||
if (collectionSlug) {
|
||||
collectionConfig = config.collections.find((collection) => collection.slug === collectionSlug)
|
||||
|
||||
if (!collectionConfig) {
|
||||
notFound()
|
||||
}
|
||||
}
|
||||
|
||||
if (globalSlug) {
|
||||
globalConfig = config.globals.find((global) => global.slug === globalSlug)
|
||||
|
||||
if (!globalConfig) {
|
||||
notFound()
|
||||
}
|
||||
}
|
||||
|
||||
if (!permissions.canAccessAdmin && !isAdminAuthRoute({ adminRoute, config, route })) {
|
||||
notFound()
|
||||
}
|
||||
|
||||
return {
|
||||
collectionConfig,
|
||||
collectionSlug,
|
||||
docID,
|
||||
globalConfig,
|
||||
globalSlug,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,22 @@
|
||||
import type { User } from 'payload'
|
||||
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import { redirect } from 'next/navigation.js'
|
||||
import * as qs from 'qs-esm'
|
||||
|
||||
import { isAdminAuthRoute, isAdminRoute } from './shared.js'
|
||||
|
||||
export const handleAuthRedirect = ({
|
||||
config,
|
||||
redirectUnauthenticatedUser,
|
||||
route,
|
||||
searchParams,
|
||||
}: {
|
||||
type Args = {
|
||||
config
|
||||
redirectUnauthenticatedUser: boolean | string
|
||||
route: string
|
||||
searchParams: { [key: string]: string | string[] }
|
||||
}) => {
|
||||
user?: User
|
||||
}
|
||||
export const handleAuthRedirect = ({ config, route, searchParams, user }: Args): string => {
|
||||
const {
|
||||
admin: {
|
||||
routes: { login: loginRouteFromConfig },
|
||||
routes: { login: loginRouteFromConfig, unauthorized: unauthorizedRoute },
|
||||
},
|
||||
routes: { admin: adminRoute },
|
||||
} = config
|
||||
|
||||
if (!isAdminAuthRoute({ adminRoute, config, route })) {
|
||||
if (searchParams && 'redirect' in searchParams) {
|
||||
delete searchParams.redirect
|
||||
}
|
||||
@@ -33,16 +27,12 @@ export const handleAuthRedirect = ({
|
||||
: undefined,
|
||||
)
|
||||
|
||||
const adminLoginRoute = formatAdminURL({ adminRoute, path: loginRouteFromConfig })
|
||||
const redirectTo = formatAdminURL({
|
||||
adminRoute,
|
||||
path: user ? unauthorizedRoute : loginRouteFromConfig,
|
||||
})
|
||||
|
||||
const customLoginRoute =
|
||||
typeof redirectUnauthenticatedUser === 'string' ? redirectUnauthenticatedUser : undefined
|
||||
|
||||
const loginRoute = isAdminRoute({ adminRoute, config, route })
|
||||
? adminLoginRoute
|
||||
: customLoginRoute || loginRouteFromConfig
|
||||
|
||||
const parsedLoginRouteSearchParams = qs.parse(loginRoute.split('?')[1] ?? '')
|
||||
const parsedLoginRouteSearchParams = qs.parse(redirectTo.split('?')[1] ?? '')
|
||||
|
||||
const searchParamsWithRedirect = `${qs.stringify(
|
||||
{
|
||||
@@ -52,6 +42,5 @@ export const handleAuthRedirect = ({
|
||||
{ addQueryPrefix: true },
|
||||
)}`
|
||||
|
||||
redirect(`${loginRoute.split('?')[0]}${searchParamsWithRedirect}`)
|
||||
}
|
||||
return `${redirectTo.split('?')[0]}${searchParamsWithRedirect}`
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { InitPageResult, Locale, PayloadRequest, VisibleEntities } from 'pa
|
||||
|
||||
import { findLocaleFromCode } from '@payloadcms/ui/shared'
|
||||
import { headers as getHeaders } from 'next/headers.js'
|
||||
import { notFound } from 'next/navigation.js'
|
||||
import { createLocalReq, isEntityHidden, parseCookies } from 'payload'
|
||||
import * as qs from 'qs-esm'
|
||||
|
||||
@@ -9,13 +10,13 @@ import type { Args } from './types.js'
|
||||
|
||||
import { getPayloadHMR } from '../getPayloadHMR.js'
|
||||
import { initReq } from '../initReq.js'
|
||||
import { handleAdminPage } from './handleAdminPage.js'
|
||||
import { getRouteInfo } from './handleAdminPage.js'
|
||||
import { handleAuthRedirect } from './handleAuthRedirect.js'
|
||||
import { isPublicAdminRoute } from './shared.js'
|
||||
|
||||
export const initPage = async ({
|
||||
config: configPromise,
|
||||
importMap,
|
||||
redirectUnauthenticatedUser = false,
|
||||
route,
|
||||
searchParams,
|
||||
}: Args): Promise<InitPageResult> => {
|
||||
@@ -128,22 +129,30 @@ export const initPage = async ({
|
||||
.filter(Boolean),
|
||||
}
|
||||
|
||||
if (redirectUnauthenticatedUser && !user) {
|
||||
handleAuthRedirect({
|
||||
let redirectTo = null
|
||||
|
||||
if (
|
||||
!permissions.canAccessAdmin &&
|
||||
!isPublicAdminRoute({ adminRoute, config: payload.config, route })
|
||||
) {
|
||||
redirectTo = handleAuthRedirect({
|
||||
config: payload.config,
|
||||
redirectUnauthenticatedUser,
|
||||
route,
|
||||
searchParams,
|
||||
user,
|
||||
})
|
||||
}
|
||||
|
||||
const { collectionConfig, docID, globalConfig } = handleAdminPage({
|
||||
const { collectionConfig, collectionSlug, docID, globalConfig, globalSlug } = getRouteInfo({
|
||||
adminRoute,
|
||||
config: payload.config,
|
||||
permissions,
|
||||
route,
|
||||
})
|
||||
|
||||
if ((collectionSlug && !collectionConfig) || (globalSlug && !globalConfig)) {
|
||||
return notFound()
|
||||
}
|
||||
|
||||
return {
|
||||
collectionConfig,
|
||||
cookies,
|
||||
@@ -152,6 +161,7 @@ export const initPage = async ({
|
||||
languageOptions,
|
||||
locale,
|
||||
permissions,
|
||||
redirectTo,
|
||||
req,
|
||||
translations: i18n.translations,
|
||||
visibleEntities,
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { SanitizedConfig } from 'payload'
|
||||
|
||||
const authRouteKeys: (keyof SanitizedConfig['admin']['routes'])[] = [
|
||||
// Routes that require admin authentication
|
||||
const publicAdminRoutes: (keyof Pick<
|
||||
SanitizedConfig['admin']['routes'],
|
||||
'createFirstUser' | 'forgot' | 'inactivity' | 'login' | 'logout' | 'reset' | 'unauthorized'
|
||||
>)[] = [
|
||||
'createFirstUser',
|
||||
'forgot',
|
||||
'login',
|
||||
@@ -13,17 +17,16 @@ const authRouteKeys: (keyof SanitizedConfig['admin']['routes'])[] = [
|
||||
|
||||
export const isAdminRoute = ({
|
||||
adminRoute,
|
||||
config,
|
||||
route,
|
||||
}: {
|
||||
adminRoute: string
|
||||
config: SanitizedConfig
|
||||
route: string
|
||||
}): boolean => {
|
||||
return route.startsWith(adminRoute) && !isAdminAuthRoute({ adminRoute, config, route })
|
||||
return route.startsWith(adminRoute)
|
||||
}
|
||||
|
||||
export const isAdminAuthRoute = ({
|
||||
export const isPublicAdminRoute = ({
|
||||
adminRoute,
|
||||
config,
|
||||
route,
|
||||
@@ -32,13 +35,17 @@ export const isAdminAuthRoute = ({
|
||||
config: SanitizedConfig
|
||||
route: string
|
||||
}): boolean => {
|
||||
const authRoutes = config.admin?.routes
|
||||
? Object.entries(config.admin.routes)
|
||||
.filter(([key]) => authRouteKeys.includes(key as keyof SanitizedConfig['admin']['routes']))
|
||||
.map(([_, value]) => value)
|
||||
: []
|
||||
|
||||
return authRoutes.some((r) => getRouteWithoutAdmin({ adminRoute, route }).startsWith(r))
|
||||
return publicAdminRoutes.some((routeSegment) => {
|
||||
const segment = config.admin?.routes?.[routeSegment] || routeSegment
|
||||
const routeWithoutAdmin = getRouteWithoutAdmin({ adminRoute, route })
|
||||
if (routeWithoutAdmin.startsWith(segment)) {
|
||||
return true
|
||||
} else if (routeWithoutAdmin.includes('/verify/')) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const getRouteWithoutAdmin = ({
|
||||
|
||||
@@ -5,7 +5,9 @@ import type { FormState, PayloadRequest } from 'payload'
|
||||
|
||||
import { EmailField, Form, FormSubmit, TextField, useConfig, useTranslation } from '@payloadcms/ui'
|
||||
import { email, text } from 'payload/shared'
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
|
||||
import { FormHeader } from '../../../elements/FormHeader/index.js'
|
||||
|
||||
export const ForgotPasswordForm: React.FC = () => {
|
||||
const { config } = useConfig()
|
||||
@@ -54,10 +56,10 @@ export const ForgotPasswordForm: React.FC = () => {
|
||||
|
||||
if (hasSubmitted) {
|
||||
return (
|
||||
<Fragment>
|
||||
<h1>{t('authentication:emailSent')}</h1>
|
||||
<p>{t('authentication:checkYourEmailForPasswordReset')}</p>
|
||||
</Fragment>
|
||||
<FormHeader
|
||||
description={t('authentication:checkYourEmailForPasswordReset')}
|
||||
heading={t('authentication:emailSent')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -68,12 +70,14 @@ export const ForgotPasswordForm: React.FC = () => {
|
||||
initialState={initialState}
|
||||
method="POST"
|
||||
>
|
||||
<h1>{t('authentication:forgotPassword')}</h1>
|
||||
<p>
|
||||
{loginWithUsername
|
||||
<FormHeader
|
||||
description={
|
||||
loginWithUsername
|
||||
? t('authentication:forgotPasswordUsernameInstructions')
|
||||
: t('authentication:forgotPasswordEmailInstructions')}
|
||||
</p>
|
||||
: t('authentication:forgotPasswordEmailInstructions')
|
||||
}
|
||||
heading={t('authentication:forgotPassword')}
|
||||
/>
|
||||
|
||||
{loginWithUsername ? (
|
||||
<TextField
|
||||
@@ -120,7 +124,7 @@ export const ForgotPasswordForm: React.FC = () => {
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<FormSubmit>{t('general:submit')}</FormSubmit>
|
||||
<FormSubmit size="large">{t('general:submit')}</FormSubmit>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { formatAdminURL, Translation } from '@payloadcms/ui/shared'
|
||||
import LinkImport from 'next/link.js'
|
||||
import React, { Fragment } from 'react'
|
||||
|
||||
import { FormHeader } from '../../elements/FormHeader/index.js'
|
||||
import { ForgotPasswordForm } from './ForgotPasswordForm/index.js'
|
||||
|
||||
export { generateForgotPasswordMetadata } from './meta.js'
|
||||
@@ -31,8 +32,8 @@ export const ForgotPasswordView: React.FC<AdminViewProps> = ({ initPageResult })
|
||||
if (user) {
|
||||
return (
|
||||
<Fragment>
|
||||
<h1>{i18n.t('authentication:alreadyLoggedIn')}</h1>
|
||||
<p>
|
||||
<FormHeader
|
||||
description={
|
||||
<Translation
|
||||
elements={{
|
||||
'0': ({ children }) => (
|
||||
@@ -49,8 +50,9 @@ export const ForgotPasswordView: React.FC<AdminViewProps> = ({ initPageResult })
|
||||
i18nKey="authentication:loggedInChangePassword"
|
||||
t={i18n.t}
|
||||
/>
|
||||
</p>
|
||||
<br />
|
||||
}
|
||||
heading={i18n.t('authentication:alreadyLoggedIn')}
|
||||
/>
|
||||
<Button buttonStyle="secondary" el="link" Link={Link} size="large" to={adminRoute}>
|
||||
{i18n.t('general:backToDashboard')}
|
||||
</Button>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
'use client'
|
||||
import type { FormState } from 'payload'
|
||||
|
||||
import {
|
||||
ConfirmPasswordField,
|
||||
Form,
|
||||
@@ -13,8 +11,8 @@ import {
|
||||
} from '@payloadcms/ui'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import { type FormState } from 'payload'
|
||||
import React from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
type Args = {
|
||||
readonly token: string
|
||||
@@ -33,7 +31,7 @@ const initialState: FormState = {
|
||||
},
|
||||
}
|
||||
|
||||
export const ResetPasswordClient: React.FC<Args> = ({ token }) => {
|
||||
export const ResetPasswordForm: React.FC<Args> = ({ token }) => {
|
||||
const i18n = useTranslation()
|
||||
const {
|
||||
config: {
|
||||
@@ -47,13 +45,11 @@ export const ResetPasswordClient: React.FC<Args> = ({ token }) => {
|
||||
} = useConfig()
|
||||
|
||||
const history = useRouter()
|
||||
|
||||
const { fetchFullUser } = useAuth()
|
||||
|
||||
const onSuccess = React.useCallback(
|
||||
async (data) => {
|
||||
if (data.token) {
|
||||
await fetchFullUser()
|
||||
const onSuccess = React.useCallback(async () => {
|
||||
const user = await fetchFullUser()
|
||||
if (user) {
|
||||
history.push(adminRoute)
|
||||
} else {
|
||||
history.push(
|
||||
@@ -62,11 +58,8 @@ export const ResetPasswordClient: React.FC<Args> = ({ token }) => {
|
||||
path: loginRoute,
|
||||
}),
|
||||
)
|
||||
toast.success(i18n.t('general:updatedSuccessfully'))
|
||||
}
|
||||
},
|
||||
[adminRoute, fetchFullUser, history, i18n, loginRoute],
|
||||
)
|
||||
}, [adminRoute, fetchFullUser, history, loginRoute])
|
||||
|
||||
return (
|
||||
<Form
|
||||
@@ -75,7 +68,7 @@ export const ResetPasswordClient: React.FC<Args> = ({ token }) => {
|
||||
method="POST"
|
||||
onSuccess={onSuccess}
|
||||
>
|
||||
<div className={'inputWrap'}>
|
||||
<div className="inputWrap">
|
||||
<PasswordField
|
||||
field={{
|
||||
name: 'password',
|
||||
@@ -1,33 +1,11 @@
|
||||
@import '../../scss/styles.scss';
|
||||
|
||||
@layer payload-default {
|
||||
.reset-password {
|
||||
&__wrap {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: base(0.8);
|
||||
max-width: base(36);
|
||||
|
||||
& > form {
|
||||
width: 100%;
|
||||
|
||||
& > .inputWrap {
|
||||
.reset-password__wrap {
|
||||
.inputWrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: base(0.8);
|
||||
|
||||
> * {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& > .btn {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ import { formatAdminURL, Translation } from '@payloadcms/ui/shared'
|
||||
import LinkImport from 'next/link.js'
|
||||
import React from 'react'
|
||||
|
||||
import { ResetPasswordClient } from './index.client.js'
|
||||
import { FormHeader } from '../../elements/FormHeader/index.js'
|
||||
import './index.scss'
|
||||
import { ResetPasswordForm } from './ResetPasswordForm/index.js'
|
||||
|
||||
export const resetPasswordBaseClass = 'reset-password'
|
||||
|
||||
@@ -29,7 +30,7 @@ export const ResetPassword: React.FC<AdminViewProps> = ({ initPageResult, params
|
||||
|
||||
const {
|
||||
admin: {
|
||||
routes: { account: accountRoute },
|
||||
routes: { account: accountRoute, login: loginRoute },
|
||||
},
|
||||
routes: { admin: adminRoute },
|
||||
} = config
|
||||
@@ -37,8 +38,8 @@ export const ResetPassword: React.FC<AdminViewProps> = ({ initPageResult, params
|
||||
if (user) {
|
||||
return (
|
||||
<div className={`${resetPasswordBaseClass}__wrap`}>
|
||||
<h1>{i18n.t('authentication:alreadyLoggedIn')}</h1>
|
||||
<p>
|
||||
<FormHeader
|
||||
description={
|
||||
<Translation
|
||||
elements={{
|
||||
'0': ({ children }) => (
|
||||
@@ -55,7 +56,9 @@ export const ResetPassword: React.FC<AdminViewProps> = ({ initPageResult, params
|
||||
i18nKey="authentication:loggedInChangePassword"
|
||||
t={i18n.t}
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
heading={i18n.t('authentication:alreadyLoggedIn')}
|
||||
/>
|
||||
<Button buttonStyle="secondary" el="link" Link={Link} size="large" to={adminRoute}>
|
||||
{i18n.t('general:backToDashboard')}
|
||||
</Button>
|
||||
@@ -65,8 +68,16 @@ export const ResetPassword: React.FC<AdminViewProps> = ({ initPageResult, params
|
||||
|
||||
return (
|
||||
<div className={`${resetPasswordBaseClass}__wrap`}>
|
||||
<h1>{i18n.t('authentication:resetPassword')}</h1>
|
||||
<ResetPasswordClient token={token} />
|
||||
<FormHeader heading={i18n.t('authentication:resetPassword')} />
|
||||
<ResetPasswordForm token={token} />
|
||||
<Link
|
||||
href={formatAdminURL({
|
||||
adminRoute,
|
||||
path: loginRoute,
|
||||
})}
|
||||
>
|
||||
{i18n.t('authentication:backToLogin')}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -92,7 +92,6 @@ export const getViewFromConfig = ({
|
||||
}
|
||||
templateClassName = 'dashboard'
|
||||
templateType = 'default'
|
||||
initPageOptions.redirectUnauthenticatedUser = true
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -132,7 +131,6 @@ export const getViewFromConfig = ({
|
||||
templateType = 'minimal'
|
||||
|
||||
if (viewKey === 'account') {
|
||||
initPageOptions.redirectUnauthenticatedUser = true
|
||||
templateType = 'default'
|
||||
}
|
||||
}
|
||||
@@ -150,7 +148,6 @@ export const getViewFromConfig = ({
|
||||
|
||||
if (isCollection) {
|
||||
// --> /collections/:collectionSlug
|
||||
initPageOptions.redirectUnauthenticatedUser = true
|
||||
|
||||
ViewToRender = {
|
||||
Component: ListView,
|
||||
@@ -160,7 +157,6 @@ export const getViewFromConfig = ({
|
||||
templateType = 'default'
|
||||
} else if (isGlobal) {
|
||||
// --> /globals/:globalSlug
|
||||
initPageOptions.redirectUnauthenticatedUser = true
|
||||
|
||||
ViewToRender = {
|
||||
Component: DocumentView,
|
||||
@@ -187,7 +183,6 @@ export const getViewFromConfig = ({
|
||||
// --> /collections/:collectionSlug/:id/versions
|
||||
// --> /collections/:collectionSlug/:id/versions/:versionId
|
||||
// --> /collections/:collectionSlug/:id/api
|
||||
initPageOptions.redirectUnauthenticatedUser = true
|
||||
|
||||
ViewToRender = {
|
||||
Component: DocumentView,
|
||||
@@ -201,7 +196,6 @@ export const getViewFromConfig = ({
|
||||
// --> /globals/:globalSlug/preview
|
||||
// --> /globals/:globalSlug/versions/:versionId
|
||||
// --> /globals/:globalSlug/api
|
||||
initPageOptions.redirectUnauthenticatedUser = true
|
||||
|
||||
ViewToRender = {
|
||||
Component: DocumentView,
|
||||
|
||||
@@ -72,6 +72,10 @@ export const RootPage = async ({
|
||||
|
||||
const initPageResult = await initPage(initPageOptions)
|
||||
|
||||
if (typeof initPageResult?.redirectTo === 'string') {
|
||||
redirect(initPageResult.redirectTo)
|
||||
}
|
||||
|
||||
if (initPageResult) {
|
||||
dbHasUser = await initPageResult?.req.payload.db
|
||||
.findOne({
|
||||
@@ -137,8 +141,8 @@ export const RootPage = async ({
|
||||
visibleEntities={{
|
||||
// The reason we are not passing in initPageResult.visibleEntities directly is due to a "Cannot assign to read only property of object '#<Object>" error introduced in React 19
|
||||
// which this caused as soon as initPageResult.visibleEntities is passed in
|
||||
collections: initPageResult.visibleEntities?.collections,
|
||||
globals: initPageResult.visibleEntities?.globals,
|
||||
collections: initPageResult?.visibleEntities?.collections,
|
||||
globals: initPageResult?.visibleEntities?.globals,
|
||||
}}
|
||||
>
|
||||
{RenderedView}
|
||||
|
||||
@@ -2,37 +2,8 @@
|
||||
|
||||
@layer payload-default {
|
||||
.unauthorized {
|
||||
margin-top: var(--base);
|
||||
|
||||
& > * {
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&--margin-top-large {
|
||||
margin-top: calc(var(--base) * 2);
|
||||
}
|
||||
|
||||
@include large-break {
|
||||
&--margin-top-large {
|
||||
margin-top: var(--base);
|
||||
}
|
||||
}
|
||||
|
||||
@include small-break {
|
||||
margin-top: calc(var(--base) / 2);
|
||||
|
||||
&--margin-top-large {
|
||||
margin-top: calc(var(--base) / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { AdminViewComponent, PayloadServerReactComponent } from 'payload'
|
||||
|
||||
import { Button, Gutter } from '@payloadcms/ui'
|
||||
import { Button } from '@payloadcms/ui'
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import LinkImport from 'next/link.js'
|
||||
import React from 'react'
|
||||
|
||||
import { FormHeader } from '../../elements/FormHeader/index.js'
|
||||
import './index.scss'
|
||||
|
||||
const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.default
|
||||
@@ -23,24 +25,31 @@ export const UnauthorizedView: PayloadServerReactComponent<AdminViewComponent> =
|
||||
admin: {
|
||||
routes: { logout: logoutRoute },
|
||||
},
|
||||
routes: { admin: adminRoute },
|
||||
},
|
||||
},
|
||||
},
|
||||
} = initPageResult
|
||||
|
||||
return (
|
||||
<Gutter className={baseClass}>
|
||||
<h2>{i18n.t('error:unauthorized')}</h2>
|
||||
<p>{i18n.t('error:notAllowedToAccessPage')}</p>
|
||||
<div className={baseClass}>
|
||||
<FormHeader
|
||||
description={i18n.t('error:notAllowedToAccessPage')}
|
||||
heading={i18n.t('error:unauthorized')}
|
||||
/>
|
||||
|
||||
<Button
|
||||
className={`${baseClass}__button`}
|
||||
el="link"
|
||||
Link={Link}
|
||||
size="large"
|
||||
to={logoutRoute}
|
||||
to={formatAdminURL({
|
||||
adminRoute,
|
||||
path: logoutRoute,
|
||||
})}
|
||||
>
|
||||
{i18n.t('authentication:logOut')}
|
||||
</Button>
|
||||
</Gutter>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
32
packages/next/src/views/Verify/index.client.tsx
Normal file
32
packages/next/src/views/Verify/index.client.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
'use client'
|
||||
import { toast } from '@payloadcms/ui'
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import React, { useEffect } from 'react'
|
||||
|
||||
type Props = {
|
||||
message: string
|
||||
redirectTo: string
|
||||
}
|
||||
export function ToastAndRedirect({ message, redirectTo }: Props) {
|
||||
const router = useRouter()
|
||||
const hasToastedRef = React.useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
let timeoutID
|
||||
if (toast) {
|
||||
timeoutID = setTimeout(() => {
|
||||
toast.success(message)
|
||||
hasToastedRef.current = true
|
||||
router.push(redirectTo)
|
||||
}, 100)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timeoutID) {
|
||||
clearTimeout(timeoutID)
|
||||
}
|
||||
}
|
||||
}, [router, redirectTo, message])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { AdminViewProps } from 'payload'
|
||||
|
||||
import { formatAdminURL } from '@payloadcms/ui/shared'
|
||||
import { redirect } from 'next/navigation.js'
|
||||
import React from 'react'
|
||||
|
||||
import { Logo } from '../../elements/Logo/index.js'
|
||||
import { ToastAndRedirect } from './index.client.js'
|
||||
import './index.scss'
|
||||
|
||||
export const verifyBaseClass = 'verify'
|
||||
@@ -33,6 +33,7 @@ export const Verify: React.FC<AdminViewProps> = async ({
|
||||
} = config
|
||||
|
||||
let textToRender
|
||||
let isVerified = false
|
||||
|
||||
try {
|
||||
await req.payload.verifyEmail({
|
||||
@@ -40,15 +41,21 @@ export const Verify: React.FC<AdminViewProps> = async ({
|
||||
token,
|
||||
})
|
||||
|
||||
return redirect(formatAdminURL({ adminRoute, path: '/login' }))
|
||||
isVerified = true
|
||||
textToRender = req.t('authentication:emailVerified')
|
||||
} catch (e) {
|
||||
// already verified
|
||||
if (e?.status === 202) {
|
||||
redirect(formatAdminURL({ adminRoute, path: '/login' }))
|
||||
}
|
||||
textToRender = req.t('authentication:unableToVerify')
|
||||
}
|
||||
|
||||
if (isVerified) {
|
||||
return (
|
||||
<ToastAndRedirect
|
||||
message={req.t('authentication:emailVerified')}
|
||||
redirectTo={formatAdminURL({ adminRoute, path: '/login' })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className={`${verifyBaseClass}__brand`}>
|
||||
|
||||
@@ -53,6 +53,7 @@ export type InitPageResult = {
|
||||
languageOptions: LanguageOptions
|
||||
locale?: Locale
|
||||
permissions: Permissions
|
||||
redirectTo?: string
|
||||
req: PayloadRequest
|
||||
translations: ClientTranslationsObject
|
||||
visibleEntities: VisibleEntities
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { Payload } from '../index.js'
|
||||
import type { SanitizedCollectionConfig } from './../collections/config/types.js'
|
||||
|
||||
type CookieOptions = {
|
||||
@@ -125,63 +124,63 @@ export const getCookieExpiration = ({ seconds = 7200 }: GetCookieExpirationArgs)
|
||||
|
||||
type GeneratePayloadCookieArgs = {
|
||||
/* The auth collection config */
|
||||
collectionConfig: SanitizedCollectionConfig
|
||||
/* An instance of payload */
|
||||
payload: Payload
|
||||
collectionAuthConfig: SanitizedCollectionConfig['auth']
|
||||
/* Prefix to scope the cookie */
|
||||
cookiePrefix: string
|
||||
/* The returnAs value */
|
||||
returnCookieAsObject?: boolean
|
||||
/* The token to be stored in the cookie */
|
||||
token: string
|
||||
}
|
||||
export const generatePayloadCookie = <T extends GeneratePayloadCookieArgs>({
|
||||
collectionConfig,
|
||||
payload,
|
||||
collectionAuthConfig,
|
||||
cookiePrefix,
|
||||
returnCookieAsObject = false,
|
||||
token,
|
||||
}: T): T['returnCookieAsObject'] extends true ? CookieObject : string => {
|
||||
const sameSite =
|
||||
typeof collectionConfig.auth.cookies.sameSite === 'string'
|
||||
? collectionConfig.auth.cookies.sameSite
|
||||
: collectionConfig.auth.cookies.sameSite
|
||||
typeof collectionAuthConfig.cookies.sameSite === 'string'
|
||||
? collectionAuthConfig.cookies.sameSite
|
||||
: collectionAuthConfig.cookies.sameSite
|
||||
? 'Strict'
|
||||
: undefined
|
||||
|
||||
return generateCookie<T['returnCookieAsObject']>({
|
||||
name: `${payload.config.cookiePrefix}-token`,
|
||||
domain: collectionConfig.auth.cookies.domain ?? undefined,
|
||||
expires: getCookieExpiration({ seconds: collectionConfig.auth.tokenExpiration }),
|
||||
name: `${cookiePrefix}-token`,
|
||||
domain: collectionAuthConfig.cookies.domain ?? undefined,
|
||||
expires: getCookieExpiration({ seconds: collectionAuthConfig.tokenExpiration }),
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
returnCookieAsObject,
|
||||
sameSite,
|
||||
secure: collectionConfig.auth.cookies.secure,
|
||||
secure: collectionAuthConfig.cookies.secure,
|
||||
value: token,
|
||||
})
|
||||
}
|
||||
|
||||
export const generateExpiredPayloadCookie = <T extends Omit<GeneratePayloadCookieArgs, 'token'>>({
|
||||
collectionConfig,
|
||||
payload,
|
||||
collectionAuthConfig,
|
||||
cookiePrefix,
|
||||
returnCookieAsObject = false,
|
||||
}: T): T['returnCookieAsObject'] extends true ? CookieObject : string => {
|
||||
const sameSite =
|
||||
typeof collectionConfig.auth.cookies.sameSite === 'string'
|
||||
? collectionConfig.auth.cookies.sameSite
|
||||
: collectionConfig.auth.cookies.sameSite
|
||||
typeof collectionAuthConfig.cookies.sameSite === 'string'
|
||||
? collectionAuthConfig.cookies.sameSite
|
||||
: collectionAuthConfig.cookies.sameSite
|
||||
? 'Strict'
|
||||
: undefined
|
||||
|
||||
const expires = new Date(Date.now() - 1000)
|
||||
|
||||
return generateCookie<T['returnCookieAsObject']>({
|
||||
name: `${payload.config.cookiePrefix}-token`,
|
||||
domain: collectionConfig.auth.cookies.domain ?? undefined,
|
||||
name: `${cookiePrefix}-token`,
|
||||
domain: collectionAuthConfig.cookies.domain ?? undefined,
|
||||
expires,
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
returnCookieAsObject,
|
||||
sameSite,
|
||||
secure: collectionConfig.auth.cookies.secure,
|
||||
secure: collectionAuthConfig.cookies.secure,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ export const resetPasswordOperation = async (args: Arguments): Promise<Result> =
|
||||
user.resetPasswordExpiration = new Date().toISOString()
|
||||
|
||||
if (collectionConfig.auth.verify) {
|
||||
user._verified = true
|
||||
user._verified = Boolean(user._verified)
|
||||
}
|
||||
|
||||
const doc = await payload.db.updateOne({
|
||||
|
||||
@@ -34,9 +34,6 @@ export const verifyEmailOperation = async (args: Args): Promise<boolean> => {
|
||||
if (!user) {
|
||||
throw new APIError('Verification token is invalid.', httpStatus.FORBIDDEN)
|
||||
}
|
||||
if (user && user._verified === true) {
|
||||
throw new APIError('This account has already been activated.', httpStatus.ACCEPTED)
|
||||
}
|
||||
|
||||
await req.payload.db.updateOne({
|
||||
id: user.id,
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
export {
|
||||
generateCookie,
|
||||
generateExpiredPayloadCookie,
|
||||
generatePayloadCookie,
|
||||
getCookieExpiration,
|
||||
parseCookies,
|
||||
} from '../auth/cookies.js'
|
||||
export { parsePayloadComponent } from '../bin/generateImportMap/parsePayloadComponent.js'
|
||||
export { defaults as collectionDefaults } from '../collections/config/defaults.js'
|
||||
|
||||
export { serverProps } from '../config/types.js'
|
||||
|
||||
export {
|
||||
@@ -20,19 +28,19 @@ export {
|
||||
tabHasName,
|
||||
valueIsValueWithRelation,
|
||||
} from '../fields/config/types.js'
|
||||
|
||||
export * from '../fields/validations.js'
|
||||
|
||||
export { validOperators } from '../types/constants.js'
|
||||
|
||||
export { formatFilesize } from '../uploads/formatFilesize.js'
|
||||
|
||||
export { isImage } from '../uploads/isImage.js'
|
||||
|
||||
export {
|
||||
deepCopyObject,
|
||||
deepCopyObjectComplex,
|
||||
deepCopyObjectSimple,
|
||||
} from '../utilities/deepCopyObject.js'
|
||||
|
||||
export {
|
||||
deepMerge,
|
||||
deepMergeWithCombinedArrays,
|
||||
@@ -41,8 +49,8 @@ export {
|
||||
} from '../utilities/deepMerge.js'
|
||||
|
||||
export { fieldSchemaToJSON } from '../utilities/fieldSchemaToJSON.js'
|
||||
|
||||
export { getDataByPath } from '../utilities/getDataByPath.js'
|
||||
|
||||
export { getSiblingData } from '../utilities/getSiblingData.js'
|
||||
|
||||
export { getUniqueListBy } from '../utilities/getUniqueListBy.js'
|
||||
@@ -66,6 +74,5 @@ export { unflatten } from '../utilities/unflatten.js'
|
||||
export { wait } from '../utilities/wait.js'
|
||||
|
||||
export { default as wordBoundariesRegex } from '../utilities/wordBoundariesRegex.js'
|
||||
|
||||
export { versionDefaults } from '../versions/defaults.js'
|
||||
export { deepMergeSimple } from '@payloadcms/translations/utilities'
|
||||
|
||||
@@ -184,7 +184,7 @@ export const arTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: 'العودة للوحة التّحكّم',
|
||||
cancel: 'إلغاء',
|
||||
changesNotSaved: 'لم يتمّ حفظ التّغييرات. إن غادرت الآن ، ستفقد تغييراتك.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'امسح الكل',
|
||||
close: 'إغلاق',
|
||||
collapse: 'طيّ',
|
||||
collections: 'المجموعات',
|
||||
@@ -407,7 +407,7 @@ export const arTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'تم الحفظ آخر مرة قبل {{distance}}',
|
||||
noFurtherVersionsFound: 'لم يتمّ العثور على نسخات أخرى',
|
||||
noRowsFound: 'لم يتمّ العثور على {{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'لم يتم اختيار {{label}}',
|
||||
preview: 'معاينة',
|
||||
previouslyPublished: 'نشر سابقا',
|
||||
problemRestoringVersion: 'حدث خطأ في استعادة هذه النّسخة',
|
||||
|
||||
@@ -186,7 +186,7 @@ export const azTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'Ləğv et',
|
||||
changesNotSaved:
|
||||
'Dəyişiklikləriniz saxlanılmayıb. İndi çıxsanız, dəyişikliklərinizi itirəcəksiniz.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Hamısını təmizlə',
|
||||
close: 'Bağla',
|
||||
collapse: 'Bağla',
|
||||
collections: 'Kolleksiyalar',
|
||||
@@ -414,7 +414,7 @@ export const azTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: '{{distance}} əvvəl son yadda saxlanıldı',
|
||||
noFurtherVersionsFound: 'Başqa versiyalar tapılmadı',
|
||||
noRowsFound: 'Heç bir {{label}} tapılmadı',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Heç bir {{label}} seçilməyib',
|
||||
preview: 'Öncədən baxış',
|
||||
previouslyPublished: 'Daha əvvəl nəşr olunmuş',
|
||||
problemRestoringVersion: 'Bu versiyanın bərpasında problem yaşandı',
|
||||
|
||||
@@ -185,7 +185,7 @@ export const bgTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: 'Обратно към таблото',
|
||||
cancel: 'Отмени',
|
||||
changesNotSaved: 'Промените ти не са запазени. Ако напуснеш сега, ще ги загубиш.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Изчисти всичко',
|
||||
close: 'Затвори',
|
||||
collapse: 'Свий',
|
||||
collections: 'Колекции',
|
||||
@@ -413,7 +413,7 @@ export const bgTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'последно запазено преди {{distance}}',
|
||||
noFurtherVersionsFound: 'Не са открити повече версии',
|
||||
noRowsFound: 'Не е открит {{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Не е избран {{label}}',
|
||||
preview: 'Предварителен преглед',
|
||||
previouslyPublished: 'Предишно публикувано',
|
||||
problemRestoringVersion: 'Имаше проблем при възстановяването на тази версия',
|
||||
|
||||
@@ -185,7 +185,7 @@ export const csTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: 'Zpět na nástěnku',
|
||||
cancel: 'Zrušit',
|
||||
changesNotSaved: 'Vaše změny nebyly uloženy. Pokud teď odejdete, ztratíte své změny.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Vymazat vše',
|
||||
close: 'Zavřít',
|
||||
collapse: 'Sbalit',
|
||||
collections: 'Kolekce',
|
||||
@@ -412,7 +412,7 @@ export const csTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Naposledy uloženo před {{distance}}',
|
||||
noFurtherVersionsFound: 'Nenalezeny další verze',
|
||||
noRowsFound: 'Nenalezen {{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Nebyl vybrán žádný {{label}}',
|
||||
preview: 'Náhled',
|
||||
previouslyPublished: 'Dříve publikováno',
|
||||
problemRestoringVersion: 'Při obnovování této verze došlo k problému',
|
||||
|
||||
@@ -190,7 +190,7 @@ export const deTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'Abbrechen',
|
||||
changesNotSaved:
|
||||
'Deine Änderungen wurden nicht gespeichert. Wenn du diese Seite verlässt, gehen deine Änderungen verloren.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Alles löschen',
|
||||
close: 'Schließen',
|
||||
collapse: 'Einklappen',
|
||||
collections: 'Sammlungen',
|
||||
@@ -418,7 +418,7 @@ export const deTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Zuletzt vor {{distance}} gespeichert',
|
||||
noFurtherVersionsFound: 'Keine weiteren Versionen vorhanden',
|
||||
noRowsFound: 'Kein {{label}} gefunden',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Kein {{label}} ausgewählt',
|
||||
preview: 'Vorschau',
|
||||
previouslyPublished: 'Zuvor Veröffentlicht',
|
||||
problemRestoringVersion: 'Es gab ein Problem bei der Wiederherstellung dieser Version',
|
||||
|
||||
@@ -66,9 +66,8 @@ export const enTranslations = {
|
||||
successfullyRegisteredFirstUser: 'Successfully registered first user.',
|
||||
successfullyUnlocked: 'Successfully unlocked',
|
||||
tokenRefreshSuccessful: 'Token refresh successful.',
|
||||
username: 'Username',
|
||||
|
||||
unableToVerify: 'Unable to Verify',
|
||||
username: 'Username',
|
||||
verified: 'Verified',
|
||||
verifiedSuccessfully: 'Verified Successfully',
|
||||
verify: 'Verify',
|
||||
|
||||
@@ -190,7 +190,7 @@ export const esTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'Cancelar',
|
||||
changesNotSaved:
|
||||
'Tus cambios no han sido guardados. Si te sales ahora, se perderán tus cambios.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Borrar todo',
|
||||
close: 'Cerrar',
|
||||
collapse: 'Colapsar',
|
||||
collections: 'Colecciones',
|
||||
@@ -418,7 +418,7 @@ export const esTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Guardado por última vez hace {{distance}}',
|
||||
noFurtherVersionsFound: 'No se encontraron más versiones',
|
||||
noRowsFound: 'No encontramos {{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'No se ha seleccionado ninguna {{etiqueta}}',
|
||||
preview: 'Previsualizar',
|
||||
previouslyPublished: 'Publicado Anteriormente',
|
||||
problemRestoringVersion: 'Ocurrió un problema al restaurar esta versión',
|
||||
|
||||
@@ -185,7 +185,7 @@ export const faTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'لغو',
|
||||
changesNotSaved:
|
||||
'تغییرات شما ذخیره نشده، اگر این برگه را ترک کنید. تمام تغییرات از دست خواهد رفت.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'همه را پاک کنید',
|
||||
close: 'بستن',
|
||||
collapse: 'بستن',
|
||||
collections: 'مجموعهها',
|
||||
@@ -411,7 +411,7 @@ export const faTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'آخرین بار {{distance}} پیش ذخیره شد',
|
||||
noFurtherVersionsFound: 'نگارش دیگری یافت نشد',
|
||||
noRowsFound: 'هیچ {{label}} یافت نشد',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'هیچ {{label}} ای انتخاب نشده است',
|
||||
preview: 'پیشنمایش',
|
||||
previouslyPublished: 'قبلا منتشر شده',
|
||||
problemRestoringVersion: 'مشکلی در بازیابی این نگارش وجود دارد',
|
||||
|
||||
@@ -193,7 +193,7 @@ export const frTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'Annuler',
|
||||
changesNotSaved:
|
||||
'Vos modifications n’ont pas été enregistrées. Vous perdrez vos modifications si vous quittez maintenant.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Tout effacer',
|
||||
close: 'Fermer',
|
||||
collapse: 'Réduire',
|
||||
collections: 'Collections',
|
||||
@@ -425,7 +425,7 @@ export const frTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Dernière sauvegarde il y a {{distance}}',
|
||||
noFurtherVersionsFound: 'Aucune autre version trouvée',
|
||||
noRowsFound: 'Aucun(e) {{label}} trouvé(e)',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Aucune {{étiquette}} sélectionnée',
|
||||
preview: 'Aperçu',
|
||||
previouslyPublished: 'Précédemment publié',
|
||||
problemRestoringVersion: 'Un problème est survenu lors de la restauration de cette version',
|
||||
|
||||
@@ -181,7 +181,7 @@ export const heTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: 'חזרה ללוח המחוונים',
|
||||
cancel: 'ביטול',
|
||||
changesNotSaved: 'השינויים שלך לא נשמרו. אם תצא כעת, תאבד את השינויים שלך.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'נקה הכל',
|
||||
close: 'סגור',
|
||||
collapse: 'כווץ',
|
||||
collections: 'אוספים',
|
||||
@@ -260,7 +260,7 @@ export const heTranslations: DefaultTranslationsObject = {
|
||||
nothingFound: 'לא נמצא כלום',
|
||||
noValue: 'אין ערך',
|
||||
of: 'מתוך',
|
||||
only: undefined,
|
||||
only: 'רק',
|
||||
open: 'פתח',
|
||||
or: 'או',
|
||||
order: 'סדר',
|
||||
@@ -401,7 +401,7 @@ export const heTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'נשמר לאחרונה לפני {{distance}}',
|
||||
noFurtherVersionsFound: 'לא נמצאו עוד גרסאות',
|
||||
noRowsFound: 'לא נמצאו {{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'לא נבחר {{תווית}}',
|
||||
preview: 'תצוגה מקדימה',
|
||||
previouslyPublished: 'פורסם בעבר',
|
||||
problemRestoringVersion: 'הייתה בעיה בשחזור הגרסה הזו',
|
||||
|
||||
@@ -34,7 +34,6 @@ export const hrTranslations: DefaultTranslationsObject = {
|
||||
generateNewAPIKey: 'Generiraj novi API ključ',
|
||||
generatingNewAPIKeyWillInvalidate:
|
||||
'Generiranje novog API ključa će <1>poništiti</1> prethodni ključ. Jeste li sigurni da želite nastaviti?',
|
||||
newAPIKeyGenerated: 'New API ključ generiran.',
|
||||
lockUntil: 'Zaključaj dok',
|
||||
logBackIn: 'Ponovno se prijavite',
|
||||
loggedIn: 'Za prijavu s drugim korisničkim računom potrebno je prvo <0>odjaviti se</0>',
|
||||
@@ -54,6 +53,7 @@ export const hrTranslations: DefaultTranslationsObject = {
|
||||
logoutUser: 'Odjava korisnika',
|
||||
newAccountCreated:
|
||||
'Novi račun je izrađen. Pristupite računu klikom na: <a href="{{serverURL}}">{{serverURL}}</a>. Molimo kliknite na sljedeću poveznicu ili zalijepite URL, koji se nalazi ispod, u preglednik da biste potvrdili svoju e-mail adresu: <a href="{{verificationURL}}">{{verificationURL}}</a><br> Nakon što potvrdite e-mail adresu, moći ćete se prijaviti.',
|
||||
newAPIKeyGenerated: 'New API ključ generiran.',
|
||||
newPassword: 'Nova lozinka',
|
||||
passed: 'Autentifikacija je prošla',
|
||||
passwordResetSuccessfully: 'Lozinka uspješno resetirana.',
|
||||
@@ -111,11 +111,11 @@ export const hrTranslations: DefaultTranslationsObject = {
|
||||
problemUploadingFile: 'Došlo je do problema pri učitavanju datoteke.',
|
||||
tokenInvalidOrExpired: 'Token je neispravan ili je istekao.',
|
||||
tokenNotProvided: 'Token nije pružen.',
|
||||
unPublishingDocument: 'Došlo je do problema pri poništavanju objave ovog dokumenta.',
|
||||
unableToDeleteCount: 'Nije moguće izbrisati {{count}} od {{total}} {{label}}.',
|
||||
unableToUpdateCount: 'Nije moguće ažurirati {{count}} od {{total}} {{label}}.',
|
||||
unauthorized: 'Neovlašteno, morate biti prijavljeni da biste uputili ovaj zahtjev.',
|
||||
unknown: 'Došlo je do nepoznate pogreške.',
|
||||
unPublishingDocument: 'Došlo je do problema pri poništavanju objave ovog dokumenta.',
|
||||
unspecific: 'Došlo je do pogreške.',
|
||||
userEmailAlreadyRegistered: 'Korisnik s navedenom e-mail adresom je već registriran.',
|
||||
userLocked: 'Ovaj korisnik je zaključan zbog previše neuspješnih pokušaja prijave.',
|
||||
@@ -186,7 +186,7 @@ export const hrTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: 'Natrag na nadzornu ploču',
|
||||
cancel: 'Otkaži',
|
||||
changesNotSaved: 'Vaše promjene nisu spremljene. Ako izađete sada, izgubit ćete promjene.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Očisti sve',
|
||||
close: 'Zatvori',
|
||||
collapse: 'Sažmi',
|
||||
collections: 'Kolekcije',
|
||||
@@ -258,11 +258,12 @@ export const hrTranslations: DefaultTranslationsObject = {
|
||||
next: 'Sljedeće',
|
||||
noFiltersSet: 'Nema postavljenih filtera',
|
||||
noLabel: '<Nema {{label}}>',
|
||||
notFound: 'Nije pronađeno',
|
||||
nothingFound: 'Ništa nije pronađeno',
|
||||
none: 'Nijedan',
|
||||
noOptions: 'Nema opcija',
|
||||
noResults: 'Nije pronađen nijedan {{label}}. Ili {{label}} još uvijek ne postoji ili nijedan od odgovara postavljenim filterima.',
|
||||
noResults:
|
||||
'Nije pronađen nijedan {{label}}. Ili {{label}} još uvijek ne postoji ili nijedan od odgovara postavljenim filterima.',
|
||||
notFound: 'Nije pronađeno',
|
||||
nothingFound: 'Ništa nije pronađeno',
|
||||
noValue: 'Bez vrijednosti',
|
||||
of: 'od',
|
||||
only: 'Samo',
|
||||
@@ -410,14 +411,14 @@ export const hrTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Zadnji put spremljeno prije {{distance}',
|
||||
noFurtherVersionsFound: 'Nisu pronađene daljnje verzije',
|
||||
noRowsFound: '{{label}} nije pronađeno',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Nije odabrana {{oznaka}}',
|
||||
preview: 'Pregled',
|
||||
previouslyPublished: 'Prethodno objavljeno',
|
||||
problemRestoringVersion: 'Nastao je problem pri vraćanju ove verzije',
|
||||
publish: 'Objaviti',
|
||||
publishChanges: 'Objavi promjene',
|
||||
published: 'Objavljeno',
|
||||
publishIn: undefined,
|
||||
publishIn: 'Objavi na {{locale}}',
|
||||
publishing: 'Objavljivanje',
|
||||
restoreAsDraft: 'Vrati kao skicu',
|
||||
restoredSuccessfully: 'Uspješno vraćeno.',
|
||||
|
||||
@@ -188,7 +188,7 @@ export const huTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'Mégsem',
|
||||
changesNotSaved:
|
||||
'A módosítások nem lettek mentve. Ha most távozik, elveszíti a változtatásokat.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Törölj mindent',
|
||||
close: 'Bezárás',
|
||||
collapse: 'Összecsukás',
|
||||
collections: 'Gyűjtemények',
|
||||
@@ -418,7 +418,7 @@ export const huTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Utoljára mentve {{distance}} órája',
|
||||
noFurtherVersionsFound: 'További verziók nem találhatók',
|
||||
noRowsFound: 'Nem található {{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Nincs {{címke}} kiválasztva',
|
||||
preview: 'Előnézet',
|
||||
previouslyPublished: 'Korábban Közzétéve',
|
||||
problemRestoringVersion: 'Hiba történt a verzió visszaállításakor',
|
||||
|
||||
@@ -189,7 +189,7 @@ export const itTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: 'Torna alla Dashboard',
|
||||
cancel: 'Cancella',
|
||||
changesNotSaved: 'Le tue modifiche non sono state salvate. Se esci ora, verranno perse.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Cancella Tutto',
|
||||
close: 'Chiudere',
|
||||
collapse: 'Comprimi',
|
||||
collections: 'Collezioni',
|
||||
@@ -418,7 +418,7 @@ export const itTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Ultimo salvataggio {{distance}} fa',
|
||||
noFurtherVersionsFound: 'Non sono state trovate ulteriori versioni',
|
||||
noRowsFound: 'Nessun {{label}} trovato',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Nessuna {{etichetta}} selezionata',
|
||||
preview: 'Anteprima',
|
||||
previouslyPublished: 'Precedentemente Pubblicato',
|
||||
problemRestoringVersion: 'Si è verificato un problema durante il ripristino di questa versione',
|
||||
|
||||
@@ -186,7 +186,7 @@ export const jaTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: 'ダッシュボードに戻る',
|
||||
cancel: 'キャンセル',
|
||||
changesNotSaved: '未保存の変更があります。このまま画面を離れると内容が失われます。',
|
||||
clearAll: undefined,
|
||||
clearAll: 'すべてクリア',
|
||||
close: '閉じる',
|
||||
collapse: '閉じる',
|
||||
collections: 'コレクション',
|
||||
@@ -412,7 +412,7 @@ export const jaTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: '{{distance}}前に最後に保存されました',
|
||||
noFurtherVersionsFound: 'その他のバージョンは見つかりませんでした。',
|
||||
noRowsFound: '{{label}} は未設定です',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: '選択された{{label}}はありません',
|
||||
preview: 'プレビュー',
|
||||
previouslyPublished: '以前に公開された',
|
||||
problemRestoringVersion: 'このバージョンの復元に問題がありました。',
|
||||
|
||||
@@ -185,7 +185,7 @@ export const koTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: '대시보드로 돌아가기',
|
||||
cancel: '취소',
|
||||
changesNotSaved: '변경 사항이 저장되지 않았습니다. 지금 떠나면 변경 사항을 잃게 됩니다.',
|
||||
clearAll: undefined,
|
||||
clearAll: '모두 지우기',
|
||||
close: '닫기',
|
||||
collapse: '접기',
|
||||
collections: '컬렉션',
|
||||
@@ -408,7 +408,7 @@ export const koTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: '마지막으로 저장한지 {{distance}} 전',
|
||||
noFurtherVersionsFound: '더 이상의 버전을 찾을 수 없습니다.',
|
||||
noRowsFound: '{{label}}을(를) 찾을 수 없음',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: '선택된 {{label}} 없음',
|
||||
preview: '미리보기',
|
||||
previouslyPublished: '이전에 발표된',
|
||||
problemRestoringVersion: '이 버전을 복원하는 중 문제가 발생했습니다.',
|
||||
|
||||
@@ -188,7 +188,7 @@ export const myTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'မလုပ်တော့ပါ။',
|
||||
changesNotSaved:
|
||||
'သင်၏ပြောင်းလဲမှုများကို မသိမ်းဆည်းရသေးပါ။ ယခု စာမျက်နှာက ထွက်လိုက်ပါက သင်၏ပြောင်းလဲမှုများ အကုန် ဆုံးရှုံးသွားပါမည်။ အကုန်နော်။',
|
||||
clearAll: undefined,
|
||||
clearAll: 'အားလုံးကိုရှင်းလင်းပါ',
|
||||
close: 'ပိတ်',
|
||||
collapse: 'ခေါက်သိမ်းပါ။',
|
||||
collections: 'စုစည်းမှူများ',
|
||||
@@ -420,7 +420,7 @@ export const myTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'နောက်ဆုံး သိမ်းချက် {{distance}} ကြာပြီး',
|
||||
noFurtherVersionsFound: 'နောက်ထပ်ဗားရှင်းများ မတွေ့ပါ။',
|
||||
noRowsFound: '{{label}} အားမတွေ့ပါ။',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Tiada {{label}} yang dipilih',
|
||||
preview: 'နမူနာပြရန်',
|
||||
previouslyPublished: 'တိုင်းရင်းသားထုတ်ဝေခဲ့',
|
||||
problemRestoringVersion: 'ဤဗားရှင်းကို ပြန်လည်ရယူရာတွင် ပြဿနာရှိနေသည်။',
|
||||
|
||||
@@ -186,7 +186,7 @@ export const nbTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'Avbryt',
|
||||
changesNotSaved:
|
||||
'Endringene dine er ikke lagret. Hvis du forlater nå, vil du miste endringene dine.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Tøm alt',
|
||||
close: 'Lukk',
|
||||
collapse: 'Skjul',
|
||||
collections: 'Samlinger',
|
||||
@@ -266,7 +266,7 @@ export const nbTranslations: DefaultTranslationsObject = {
|
||||
nothingFound: 'Ingenting funnet',
|
||||
noValue: 'Ingen verdi',
|
||||
of: 'av',
|
||||
only: undefined,
|
||||
only: 'Bare',
|
||||
open: 'Åpne',
|
||||
or: 'Eller',
|
||||
order: 'Rekkefølge',
|
||||
@@ -414,7 +414,7 @@ export const nbTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Sist lagret {{distance}} siden',
|
||||
noFurtherVersionsFound: 'Ingen flere versjoner funnet',
|
||||
noRowsFound: 'Ingen {{label}} funnet',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Ingen {{label}} valgt',
|
||||
preview: 'Forhåndsvisning',
|
||||
previouslyPublished: 'Tidligere Publisert',
|
||||
problemRestoringVersion: 'Det oppstod et problem med gjenoppretting av denne versjonen',
|
||||
|
||||
@@ -188,7 +188,7 @@ export const nlTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'Annuleren',
|
||||
changesNotSaved:
|
||||
'Uw wijzigingen zijn niet bewaard. Als u weggaat zullen de wijzigingen verloren gaan.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Alles wissen',
|
||||
close: 'Dichtbij',
|
||||
collapse: 'Samenvouwen',
|
||||
collections: 'Collecties',
|
||||
@@ -417,7 +417,7 @@ export const nlTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Laatst opgeslagen {{distance}} geleden',
|
||||
noFurtherVersionsFound: 'Geen verdere versies gevonden',
|
||||
noRowsFound: 'Geen {{label}} gevonden',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Geen {{label}} geselecteerd',
|
||||
preview: 'Voorbeeld',
|
||||
previouslyPublished: 'Eerder gepubliceerd',
|
||||
problemRestoringVersion: 'Er was een probleem bij het herstellen van deze versie',
|
||||
|
||||
@@ -186,7 +186,7 @@ export const plTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'Anuluj',
|
||||
changesNotSaved:
|
||||
'Twoje zmiany nie zostały zapisane. Jeśli teraz wyjdziesz, stracisz swoje zmiany.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Wyczyść wszystko',
|
||||
close: 'Zamknij',
|
||||
collapse: 'Zwiń',
|
||||
collections: 'Kolekcje',
|
||||
@@ -414,14 +414,14 @@ export const plTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Ostatnio zapisane {{distance}} temu',
|
||||
noFurtherVersionsFound: 'Nie znaleziono dalszych wersji',
|
||||
noRowsFound: 'Nie znaleziono {{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Nie wybrano {{etykieta}}',
|
||||
preview: 'Podgląd',
|
||||
previouslyPublished: 'Wcześniej opublikowane',
|
||||
problemRestoringVersion: 'Wystąpił problem podczas przywracania tej wersji',
|
||||
publish: 'Publikuj',
|
||||
publishChanges: 'Opublikuj zmiany',
|
||||
published: 'Opublikowano',
|
||||
publishIn: undefined,
|
||||
publishIn: 'Opublikuj w {{locale}}',
|
||||
publishing: 'Publikacja',
|
||||
restoreAsDraft: 'Przywróć jako szkic',
|
||||
restoredSuccessfully: 'Przywrócono pomyślnie.',
|
||||
|
||||
@@ -187,7 +187,7 @@ export const ptTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'Cancelar',
|
||||
changesNotSaved:
|
||||
'Suas alterações não foram salvas. Se você sair agora, essas alterações serão perdidas.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Limpar Tudo',
|
||||
close: 'Fechar',
|
||||
collapse: 'Recolher',
|
||||
collections: 'Coleções',
|
||||
@@ -267,7 +267,7 @@ export const ptTranslations: DefaultTranslationsObject = {
|
||||
nothingFound: 'Nada encontrado',
|
||||
noValue: 'Nenhum valor',
|
||||
of: 'de',
|
||||
only: undefined,
|
||||
only: 'Apenas',
|
||||
open: 'Abrir',
|
||||
or: 'Ou',
|
||||
order: 'Ordem',
|
||||
@@ -415,14 +415,14 @@ export const ptTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Última gravação há {{distance}}',
|
||||
noFurtherVersionsFound: 'Nenhuma outra versão encontrada',
|
||||
noRowsFound: 'Nenhum(a) {{label}} encontrado(a)',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Nenhum {{rótulo}} selecionado',
|
||||
preview: 'Pré-visualização',
|
||||
previouslyPublished: 'Publicado Anteriormente',
|
||||
problemRestoringVersion: 'Ocorreu um problema ao restaurar essa versão',
|
||||
publish: 'Publicar',
|
||||
publishChanges: 'Publicar alterações',
|
||||
published: 'Publicado',
|
||||
publishIn: undefined,
|
||||
publishIn: 'Publicar em {{locale}}',
|
||||
publishing: 'Publicação',
|
||||
restoreAsDraft: 'Restaurar como rascunho',
|
||||
restoredSuccessfully: 'Restaurado com sucesso.',
|
||||
|
||||
@@ -190,7 +190,7 @@ export const roTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'Anulați',
|
||||
changesNotSaved:
|
||||
'Modificările dvs. nu au fost salvate. Dacă plecați acum, vă veți pierde modificările.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Șterge tot',
|
||||
close: 'Închide',
|
||||
collapse: 'Colaps',
|
||||
collections: 'Colecții',
|
||||
@@ -422,7 +422,7 @@ export const roTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Ultima salvare acum {{distance}}',
|
||||
noFurtherVersionsFound: 'Nu s-au găsit alte versiuni',
|
||||
noRowsFound: 'Nu s-a găsit niciun {{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Niciun {{etichetă}} selectat',
|
||||
preview: 'Previzualizare',
|
||||
previouslyPublished: 'Publicat anterior',
|
||||
problemRestoringVersion: 'A existat o problemă la restaurarea acestei versiuni',
|
||||
|
||||
@@ -185,7 +185,7 @@ export const rsTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: 'Назад на контролни панел',
|
||||
cancel: 'Откажи',
|
||||
changesNotSaved: 'Ваше промене нису сачуване. Ако изађете сада, изгубићете промене.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Obriši sve',
|
||||
close: 'Затвори',
|
||||
collapse: 'Скупи',
|
||||
collections: 'Колекције',
|
||||
@@ -265,7 +265,7 @@ export const rsTranslations: DefaultTranslationsObject = {
|
||||
nothingFound: 'Ништа није пронађено',
|
||||
noValue: 'Без вредности',
|
||||
of: 'Од',
|
||||
only: undefined,
|
||||
only: 'Samo',
|
||||
open: 'Отвори',
|
||||
or: 'Или',
|
||||
order: 'Редослед',
|
||||
@@ -409,14 +409,14 @@ export const rsTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Задњи пут сачувано пре {{distance}',
|
||||
noFurtherVersionsFound: 'Нису пронађене наредне верзије',
|
||||
noRowsFound: '{{label}} није пронађено',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Nije odabrana {{label}}',
|
||||
preview: 'Преглед',
|
||||
previouslyPublished: 'Prethodno objavljeno',
|
||||
problemRestoringVersion: 'Настао је проблем при враћању ове верзије',
|
||||
publish: 'Објавити',
|
||||
publishChanges: 'Објави промене',
|
||||
published: 'Објављено',
|
||||
publishIn: undefined,
|
||||
publishIn: 'Objavi na {{locale}}',
|
||||
publishing: 'Objavljivanje',
|
||||
restoreAsDraft: 'Vrati kao nacrt',
|
||||
restoredSuccessfully: 'Успешно враћено.',
|
||||
|
||||
@@ -185,7 +185,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: 'Nazad na kontrolni panel',
|
||||
cancel: 'Otkaži',
|
||||
changesNotSaved: 'Vaše promene nisu sačuvane. Ako izađete sada, izgubićete promene.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Očisti sve',
|
||||
close: 'Zatvori',
|
||||
collapse: 'Skupi',
|
||||
collections: 'Kolekcije',
|
||||
@@ -265,7 +265,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
|
||||
nothingFound: 'Ništa nije pronađeno',
|
||||
noValue: 'Bez vrednosti',
|
||||
of: 'Od',
|
||||
only: undefined,
|
||||
only: 'Samo',
|
||||
open: 'Otvori',
|
||||
or: 'Ili',
|
||||
order: 'Redosled',
|
||||
@@ -410,7 +410,7 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Zadnji put sačuvano pre {{distance}',
|
||||
noFurtherVersionsFound: 'Nisu pronađene naredne verzije',
|
||||
noRowsFound: '{{label}} nije pronađeno',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Nije odabrana {{label}}',
|
||||
preview: 'Pregled',
|
||||
previouslyPublished: 'Prethodno objavljeno',
|
||||
problemRestoringVersion: 'Nastao je problem pri vraćanju ove verzije',
|
||||
|
||||
@@ -188,7 +188,7 @@ export const ruTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'Отмена',
|
||||
changesNotSaved:
|
||||
'Ваши изменения не были сохранены. Если вы сейчас уйдете, то потеряете свои изменения.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Очистить все',
|
||||
close: 'Закрыть',
|
||||
collapse: 'Свернуть',
|
||||
collections: 'Коллекции',
|
||||
@@ -416,7 +416,7 @@ export const ruTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Последний раз сохранено {{distance}} назад',
|
||||
noFurtherVersionsFound: 'Другие версии не найдены',
|
||||
noRowsFound: 'Не найдено {{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Не выбран {{label}}',
|
||||
preview: 'Предпросмотр',
|
||||
previouslyPublished: 'Ранее опубликовано',
|
||||
problemRestoringVersion: 'Возникла проблема с восстановлением этой версии',
|
||||
|
||||
@@ -187,7 +187,7 @@ export const skTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: 'Späť na nástenku',
|
||||
cancel: 'Zrušiť',
|
||||
changesNotSaved: 'Vaše zmeny neboli uložené. Ak teraz odídete, stratíte svoje zmeny.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Vymazať všetko',
|
||||
close: 'Zavrieť',
|
||||
collapse: 'Zbaliť',
|
||||
collections: 'Kolekcia',
|
||||
@@ -267,7 +267,7 @@ export const skTranslations: DefaultTranslationsObject = {
|
||||
nothingFound: 'Nič nenájdené',
|
||||
noValue: 'Žiadna hodnota',
|
||||
of: 'z',
|
||||
only: undefined,
|
||||
only: 'Iba',
|
||||
open: 'Otvoriť',
|
||||
or: 'Alebo',
|
||||
order: 'Poradie',
|
||||
@@ -414,7 +414,7 @@ export const skTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Naposledy uložené pred {{distance}}',
|
||||
noFurtherVersionsFound: 'Nenájdené ďalšie verzie',
|
||||
noRowsFound: 'Nenájdené {{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Nie je vybraté žiadne {{označenie}}',
|
||||
preview: 'Náhľad',
|
||||
previouslyPublished: 'Predtým publikované',
|
||||
problemRestoringVersion: 'Pri obnovovaní tejto verzie došlo k problému',
|
||||
|
||||
@@ -186,7 +186,7 @@ export const svTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'Avbryt',
|
||||
changesNotSaved:
|
||||
'Dina ändringar har inte sparats. Om du lämnar nu kommer du att förlora dina ändringar.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Rensa alla',
|
||||
close: 'Stänga',
|
||||
collapse: 'Kollapsa',
|
||||
collections: 'Samlingar',
|
||||
@@ -413,7 +413,7 @@ export const svTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Senast sparad för {{distance}} sedan',
|
||||
noFurtherVersionsFound: 'Inga fler versioner hittades',
|
||||
noRowsFound: 'Inga {{label}} hittades',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Inget {{etikett}} valt',
|
||||
preview: 'Förhandsvisa',
|
||||
previouslyPublished: 'Tidigare publicerad',
|
||||
problemRestoringVersion: 'Det uppstod ett problem när den här versionen skulle återställas',
|
||||
|
||||
@@ -182,7 +182,7 @@ export const thTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: 'กลับไปหน้าแดชบอร์ด',
|
||||
cancel: 'ยกเลิก',
|
||||
changesNotSaved: 'การเปลี่ยนแปลงยังไม่ได้ถูกบันทึก ถ้าคุณออกตอนนี้ สิ่งที่แก้ไขไว้จะหายไป',
|
||||
clearAll: undefined,
|
||||
clearAll: 'ล้างทั้งหมด',
|
||||
close: 'ปิด',
|
||||
collapse: 'ยุบ',
|
||||
collections: 'Collections',
|
||||
@@ -405,7 +405,7 @@ export const thTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'บันทึกครั้งล่าสุด {{distance}} ที่ผ่านมา',
|
||||
noFurtherVersionsFound: 'ไม่พบเวอร์ชันอื่น ๆ',
|
||||
noRowsFound: 'ไม่พบ {{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'ไม่มี {{label}} ที่ถูกเลือก',
|
||||
preview: 'ตัวอย่าง',
|
||||
previouslyPublished: 'เผยแพร่ก่อนหน้านี้',
|
||||
problemRestoringVersion: 'เกิดปัญหาระหว่างการกู้คืนเวอร์ชันนี้',
|
||||
|
||||
@@ -189,7 +189,7 @@ export const trTranslations: DefaultTranslationsObject = {
|
||||
cancel: 'İptal',
|
||||
changesNotSaved:
|
||||
'Değişiklikleriniz henüz kaydedilmedi. Eğer bu sayfayı terk ederseniz değişiklikleri kaybedeceksiniz.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Hepsini Temizle',
|
||||
close: 'Kapat',
|
||||
collapse: 'Daralt',
|
||||
collections: 'Koleksiyonlar',
|
||||
@@ -415,7 +415,7 @@ export const trTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Son kaydedildi {{distance}} önce',
|
||||
noFurtherVersionsFound: 'Başka sürüm bulunamadı.',
|
||||
noRowsFound: '{{label}} bulunamadı',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Seçilen {{label}} yok',
|
||||
preview: 'Önizleme',
|
||||
previouslyPublished: 'Daha Önce Yayınlanmış',
|
||||
problemRestoringVersion: 'Bu sürüme geri döndürürken bir hatayla karşılaşıldı.',
|
||||
|
||||
@@ -186,7 +186,7 @@ export const ukTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: 'Повернутись до головної сторінки',
|
||||
cancel: 'Скасувати',
|
||||
changesNotSaved: 'Ваши зміни не були збережені. Якщо ви вийдете зараз, то втратите свої зміни.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Очистити все',
|
||||
close: 'Закрити',
|
||||
collapse: 'Згорнути',
|
||||
collections: 'Колекції',
|
||||
@@ -413,7 +413,7 @@ export const ukTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Востаннє збережено {{distance}} тому',
|
||||
noFurtherVersionsFound: 'Інших версій не знайдено',
|
||||
noRowsFound: 'Не знайдено {{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Не вибрано {{label}}',
|
||||
preview: 'Попередній перегляд',
|
||||
previouslyPublished: 'Раніше опубліковано',
|
||||
problemRestoringVersion: 'Виникла проблема з відновленням цієї версії',
|
||||
|
||||
@@ -184,7 +184,7 @@ export const viTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: 'Quay lại bảng điều khiển',
|
||||
cancel: 'Hủy',
|
||||
changesNotSaved: 'Thay đổi chưa được lưu lại. Bạn sẽ mất bản chỉnh sửa nếu thoát bây giờ.',
|
||||
clearAll: undefined,
|
||||
clearAll: 'Xóa tất cả',
|
||||
close: 'Gần',
|
||||
collapse: 'Thu gọn',
|
||||
collections: 'Collections',
|
||||
@@ -264,7 +264,7 @@ export const viTranslations: DefaultTranslationsObject = {
|
||||
nothingFound: 'Không tìm thấy',
|
||||
noValue: 'Không có giá trị',
|
||||
of: 'trong số',
|
||||
only: undefined,
|
||||
only: 'Chỉ',
|
||||
open: 'Mở',
|
||||
or: 'hoặc',
|
||||
order: 'Thứ tự',
|
||||
@@ -408,7 +408,7 @@ export const viTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: 'Lần lưu cuối cùng {{distance}} trước đây',
|
||||
noFurtherVersionsFound: 'Không tìm thấy phiên bản cũ hơn',
|
||||
noRowsFound: 'Không tìm thấy: {{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: 'Không có {{label}} được chọn',
|
||||
preview: 'Bản xem trước',
|
||||
previouslyPublished: 'Đã xuất bản trước đây',
|
||||
problemRestoringVersion: 'Đã xảy ra vấn đề khi khôi phục phiên bản này',
|
||||
|
||||
@@ -179,7 +179,7 @@ export const zhTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: '返回到仪表板',
|
||||
cancel: '取消',
|
||||
changesNotSaved: '您的更改尚未保存。您确定要离开吗?',
|
||||
clearAll: undefined,
|
||||
clearAll: '清除全部',
|
||||
close: '关闭',
|
||||
collapse: '折叠',
|
||||
collections: '集合',
|
||||
@@ -258,7 +258,7 @@ export const zhTranslations: DefaultTranslationsObject = {
|
||||
nothingFound: '没有找到任何东西',
|
||||
noValue: '没有值',
|
||||
of: '的',
|
||||
only: undefined,
|
||||
only: '仅',
|
||||
open: '打开',
|
||||
or: '或',
|
||||
order: '排序',
|
||||
@@ -398,14 +398,14 @@ export const zhTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: '上次保存{{distance}}之前',
|
||||
noFurtherVersionsFound: '没有发现其他版本',
|
||||
noRowsFound: '没有发现{{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: '未选择{{label}}',
|
||||
preview: '预览',
|
||||
previouslyPublished: '先前发布过的',
|
||||
problemRestoringVersion: '恢复这个版本时发生了问题',
|
||||
publish: '发布',
|
||||
publishChanges: '发布修改',
|
||||
published: '已发布',
|
||||
publishIn: undefined,
|
||||
publishIn: '在{{locale}}发布',
|
||||
publishing: '发布',
|
||||
restoreAsDraft: '恢复为草稿',
|
||||
restoredSuccessfully: '恢复成功。',
|
||||
|
||||
@@ -179,7 +179,7 @@ export const zhTwTranslations: DefaultTranslationsObject = {
|
||||
backToDashboard: '返回到控制面板',
|
||||
cancel: '取消',
|
||||
changesNotSaved: '您還有尚未儲存的變更。您確定要離開嗎?',
|
||||
clearAll: undefined,
|
||||
clearAll: '清除全部',
|
||||
close: '關閉',
|
||||
collapse: '折疊',
|
||||
collections: '集合',
|
||||
@@ -398,14 +398,14 @@ export const zhTwTranslations: DefaultTranslationsObject = {
|
||||
lastSavedAgo: '上次儲存在{{distance}}之前',
|
||||
noFurtherVersionsFound: '沒有發現其他版本',
|
||||
noRowsFound: '沒有發現{{label}}',
|
||||
noRowsSelected: undefined,
|
||||
noRowsSelected: '未選擇 {{label}}',
|
||||
preview: '預覽',
|
||||
previouslyPublished: '先前出版過的',
|
||||
problemRestoringVersion: '回復這個版本時發生了問題',
|
||||
publish: '發佈',
|
||||
publishChanges: '發佈修改',
|
||||
published: '已發佈',
|
||||
publishIn: undefined,
|
||||
publishIn: '在 {{locale}} 發佈',
|
||||
publishing: '發布',
|
||||
restoreAsDraft: '恢復為草稿',
|
||||
restoredSuccessfully: '回復成功。',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
'use client'
|
||||
import type { ClientUser, MeOperationResult, Permissions } from 'payload'
|
||||
import type { ClientUser, MeOperationResult, Permissions, User } from 'payload'
|
||||
|
||||
import { useModal } from '@faceless-ui/modal'
|
||||
import { usePathname, useRouter } from 'next/navigation.js'
|
||||
@@ -15,7 +15,7 @@ import { formatAdminURL } from '../../utilities/formatAdminURL.js'
|
||||
import { useConfig } from '../Config/index.js'
|
||||
|
||||
export type AuthContext<T = ClientUser> = {
|
||||
fetchFullUser: () => Promise<void>
|
||||
fetchFullUser: () => Promise<null | User>
|
||||
logOut: () => Promise<void>
|
||||
permissions?: Permissions
|
||||
refreshCookie: (forceRefresh?: boolean) => void
|
||||
@@ -227,6 +227,7 @@ export function AuthProvider({
|
||||
const fetchFullUser = React.useCallback(async () => {
|
||||
try {
|
||||
const request = await requests.get(`${serverURL}${apiRoute}/${userSlug}/me`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
},
|
||||
@@ -234,9 +235,11 @@ export function AuthProvider({
|
||||
|
||||
if (request.status === 200) {
|
||||
const json: MeOperationResult = await request.json()
|
||||
let user = null
|
||||
|
||||
if (json?.user) {
|
||||
setUser(json.user)
|
||||
user = json.user
|
||||
|
||||
if (json?.token) {
|
||||
setTokenAndExpiration(json)
|
||||
@@ -245,10 +248,14 @@ export function AuthProvider({
|
||||
setUser(null)
|
||||
revokeTokenAndExpire()
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
||||
} catch (e) {
|
||||
toast.error(`Fetching user failed: ${e.message}`)
|
||||
}
|
||||
|
||||
return null
|
||||
}, [serverURL, apiRoute, userSlug, i18n.language, setTokenAndExpiration, revokeTokenAndExpire])
|
||||
|
||||
// On mount, get user and set
|
||||
|
||||
@@ -482,7 +482,7 @@ describe('access control', () => {
|
||||
serverURL,
|
||||
})
|
||||
|
||||
await expect(page.locator('.next-error-h1')).toBeVisible()
|
||||
await expect(page.locator('.unauthorized')).toBeVisible()
|
||||
|
||||
await page.goto(logoutURL)
|
||||
await page.waitForURL(logoutURL)
|
||||
@@ -500,6 +500,7 @@ describe('access control', () => {
|
||||
|
||||
test('should block admin access to non-admin user', async () => {
|
||||
const adminURL = `${serverURL}/admin`
|
||||
const unauthorizedURL = `${serverURL}/admin/unauthorized`
|
||||
await page.goto(adminURL)
|
||||
await page.waitForURL(adminURL)
|
||||
|
||||
@@ -527,9 +528,9 @@ describe('access control', () => {
|
||||
])
|
||||
|
||||
await page.goto(adminURL)
|
||||
await page.waitForURL(adminURL)
|
||||
await page.waitForURL(unauthorizedURL)
|
||||
|
||||
await expect(page.locator('.next-error-h1')).toBeVisible()
|
||||
await expect(page.locator('.unauthorized')).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -310,6 +310,7 @@ export function initPageConsoleErrorCatch(page: Page) {
|
||||
!msg.text().includes('the server responded with a status of') &&
|
||||
!msg.text().includes('Failed to fetch RSC payload for') &&
|
||||
!msg.text().includes('Error: NEXT_NOT_FOUND') &&
|
||||
!msg.text().includes('Error: NEXT_REDIRECT') &&
|
||||
!msg.text().includes('Error getting document data')
|
||||
) {
|
||||
// "Failed to fetch RSC payload for" happens seemingly randomly. There are lots of issues in the next.js repository for this. Causes e2e tests to fail and flake. Will ignore for now
|
||||
|
||||
Reference in New Issue
Block a user