Compare commits
6 Commits
v3.24.0
...
fix/checkD
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
764c42eb80 | ||
|
|
7626ff4635 | ||
|
|
9595e74153 | ||
|
|
58243a20e2 | ||
|
|
a62d86fd15 | ||
|
|
0a664f9bac |
@@ -1054,13 +1054,13 @@ import { usePayloadAPI } from '@payloadcms/ui'
|
||||
|
||||
const MyComponent: React.FC = () => {
|
||||
// Fetch data from a collection item using its ID
|
||||
const [{ data, isError, isLoading }, { setParams }] = usePayloadAPI(
|
||||
const [{ data, error, isLoading }, { setParams }] = usePayloadAPI(
|
||||
'/api/posts/123',
|
||||
{ initialParams: { depth: 1 } }
|
||||
)
|
||||
|
||||
if (isLoading) return <p>Loading...</p>
|
||||
if (isError) return <p>Error occurred while fetching data.</p>
|
||||
if (error) return <p>Error: {error.message}</p>
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -1094,7 +1094,7 @@ The first item in the returned array is an object containing the following prope
|
||||
| Property | Description |
|
||||
| --------------- | -------------------------------------------------------- |
|
||||
| **`data`** | The API response data. |
|
||||
| **`isError`** | A boolean indicating whether the request failed. |
|
||||
| **`error`** | If an error occurs, this contains the error object. |
|
||||
| **`isLoading`** | A boolean indicating whether the request is in progress. |
|
||||
|
||||
The second item is an object with the following methods:
|
||||
|
||||
@@ -242,6 +242,4 @@ You can customise the available list of timezones in the [global admin config](.
|
||||
<Banner type='info'>
|
||||
**Good to know:**
|
||||
The date itself will be stored in UTC so it's up to you to handle the conversion to the user's timezone when displaying the date in your frontend.
|
||||
|
||||
Dates without a specific time are normalised to 12:00 in the selected timezone.
|
||||
</Banner>
|
||||
|
||||
@@ -50,7 +50,6 @@ export const MyRadioField: Field = {
|
||||
| **`admin`** | Admin-specific configuration. [More details](#admin-options). |
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
|
||||
@@ -53,7 +53,6 @@ export const MySelectField: Field = {
|
||||
| **`custom`** | Extension point for adding custom data (e.g. for plugins) |
|
||||
| **`enumName`** | Custom enum name for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
| **`dbName`** | Custom table name (if `hasMany` set to `true`) for this field when using SQL Database Adapter ([Postgres](/docs/database/postgres)). Auto-generated from name if not defined. |
|
||||
| **`interfaceName`** | Create a top level, reusable [Typescript interface](/docs/typescript/generating-types#custom-field-interfaces) & [GraphQL type](/docs/graphql/graphql-schema#custom-field-schemas). |
|
||||
| **`typescriptSchema`** | Override field type generation with providing a JSON schema |
|
||||
| **`virtual`** | Provide `true` to disable field in the database. See [Virtual Fields](https://payloadcms.com/blog/learn-how-virtual-fields-can-help-solve-common-cms-challenges) |
|
||||
|
||||
|
||||
@@ -256,14 +256,18 @@ If you are using relationships or uploads in your front-end application, and you
|
||||
// ...
|
||||
// If your site is running on a different domain than your Payload server,
|
||||
// This will allows requests to be made between the two domains
|
||||
cors: [
|
||||
'http://localhost:3001' // Your front-end application
|
||||
],
|
||||
cors: {
|
||||
[
|
||||
'http://localhost:3001' // Your front-end application
|
||||
],
|
||||
},
|
||||
// If you are protecting resources behind user authentication,
|
||||
// This will allow cookies to be sent between the two domains
|
||||
csrf: [
|
||||
'http://localhost:3001' // Your front-end application
|
||||
],
|
||||
csrf: {
|
||||
[
|
||||
'http://localhost:3001' // Your front-end application
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload-monorepo",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "create-payload-app",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-mongodb",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "The officially supported MongoDB database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-postgres",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "The officially supported Postgres database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-sqlite",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "The officially supported SQLite database adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/db-vercel-postgres",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Vercel Postgres adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/drizzle",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "A library of shared functions used by different payload database adapters",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-nodemailer",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Payload Nodemailer Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/email-resend",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Payload Resend Email Adapter",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/graphql",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-react",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "The official React SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview-vue",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "The official Vue SDK for Payload Live Preview",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/live-preview",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "The official live preview JavaScript SDK for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/next",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -96,6 +96,7 @@
|
||||
"qs-esm": "7.0.2",
|
||||
"react-diff-viewer-continued": "4.0.4",
|
||||
"sass": "1.77.4",
|
||||
"sonner": "^1.7.0",
|
||||
"uuid": "10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
MinimizeMaximizeIcon,
|
||||
NumberField,
|
||||
SetDocumentStepNav,
|
||||
toast,
|
||||
useConfig,
|
||||
useDocumentInfo,
|
||||
useLocale,
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
} from '@payloadcms/ui'
|
||||
import { useSearchParams } from 'next/navigation.js'
|
||||
import * as React from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import './index.scss'
|
||||
import { LocaleSelector } from './LocaleSelector/index.js'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
'use client'
|
||||
import type { OnConfirm } from '@payloadcms/ui'
|
||||
import type { User } from 'payload'
|
||||
|
||||
import { Button, ConfirmationModal, toast, useModal, useTranslation } from '@payloadcms/ui'
|
||||
@@ -14,46 +15,54 @@ export const ResetPreferences: React.FC<{
|
||||
const { openModal } = useModal()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleResetPreferences = useCallback(async () => {
|
||||
if (!user) {
|
||||
return
|
||||
}
|
||||
const handleResetPreferences: OnConfirm = useCallback(
|
||||
async ({ closeConfirmationModal, setConfirming }) => {
|
||||
if (!user) {
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
return
|
||||
}
|
||||
|
||||
const stringifiedQuery = qs.stringify(
|
||||
{
|
||||
depth: 0,
|
||||
where: {
|
||||
user: {
|
||||
id: {
|
||||
equals: user.id,
|
||||
const stringifiedQuery = qs.stringify(
|
||||
{
|
||||
depth: 0,
|
||||
where: {
|
||||
user: {
|
||||
id: {
|
||||
equals: user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
)
|
||||
{ addQueryPrefix: true },
|
||||
)
|
||||
|
||||
try {
|
||||
const res = await fetch(`${apiRoute}/payload-preferences${stringifiedQuery}`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'DELETE',
|
||||
})
|
||||
try {
|
||||
const res = await fetch(`${apiRoute}/payload-preferences${stringifiedQuery}`, {
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'DELETE',
|
||||
})
|
||||
|
||||
const json = await res.json()
|
||||
const message = json.message
|
||||
const json = await res.json()
|
||||
const message = json.message
|
||||
|
||||
if (res.ok) {
|
||||
toast.success(message)
|
||||
} else {
|
||||
toast.error(message)
|
||||
if (res.ok) {
|
||||
toast.success(message)
|
||||
} else {
|
||||
toast.error(message)
|
||||
}
|
||||
} catch (_err) {
|
||||
// swallow error
|
||||
} finally {
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
}
|
||||
} catch (_err) {
|
||||
// swallow error
|
||||
}
|
||||
}, [apiRoute, user])
|
||||
},
|
||||
[apiRoute, user],
|
||||
)
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { sanitizeID } from '@payloadcms/ui/shared'
|
||||
import {
|
||||
combineQueries,
|
||||
extractAccessFromPermission,
|
||||
type Payload,
|
||||
type SanitizedCollectionConfig,
|
||||
type SanitizedDocumentPermissions,
|
||||
type SanitizedGlobalConfig,
|
||||
type TypedUser,
|
||||
import type {
|
||||
Payload,
|
||||
SanitizedCollectionConfig,
|
||||
SanitizedDocumentPermissions,
|
||||
SanitizedGlobalConfig,
|
||||
TypedUser,
|
||||
} from 'payload'
|
||||
|
||||
import { sanitizeID } from '@payloadcms/ui/shared'
|
||||
|
||||
type Args = {
|
||||
collectionConfig?: SanitizedCollectionConfig
|
||||
/**
|
||||
@@ -135,18 +134,15 @@ export const getVersions = async ({
|
||||
autosave: true,
|
||||
},
|
||||
user,
|
||||
where: combineQueries(
|
||||
{
|
||||
and: [
|
||||
{
|
||||
parent: {
|
||||
equals: id,
|
||||
},
|
||||
where: {
|
||||
and: [
|
||||
{
|
||||
parent: {
|
||||
equals: id,
|
||||
},
|
||||
],
|
||||
},
|
||||
extractAccessFromPermission(docPermissions.readVersions),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
if (
|
||||
@@ -162,28 +158,25 @@ export const getVersions = async ({
|
||||
;({ totalDocs: unpublishedVersionCount } = await payload.countVersions({
|
||||
collection: collectionConfig.slug,
|
||||
user,
|
||||
where: combineQueries(
|
||||
{
|
||||
and: [
|
||||
{
|
||||
parent: {
|
||||
equals: id,
|
||||
},
|
||||
where: {
|
||||
and: [
|
||||
{
|
||||
parent: {
|
||||
equals: id,
|
||||
},
|
||||
{
|
||||
'version._status': {
|
||||
equals: 'draft',
|
||||
},
|
||||
},
|
||||
{
|
||||
'version._status': {
|
||||
equals: 'draft',
|
||||
},
|
||||
{
|
||||
updatedAt: {
|
||||
greater_than: publishedDoc.updatedAt,
|
||||
},
|
||||
},
|
||||
{
|
||||
updatedAt: {
|
||||
greater_than: publishedDoc.updatedAt,
|
||||
},
|
||||
],
|
||||
},
|
||||
extractAccessFromPermission(docPermissions.readVersions),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -192,18 +185,15 @@ export const getVersions = async ({
|
||||
collection: collectionConfig.slug,
|
||||
depth: 0,
|
||||
user,
|
||||
where: combineQueries(
|
||||
{
|
||||
and: [
|
||||
{
|
||||
parent: {
|
||||
equals: id,
|
||||
},
|
||||
where: {
|
||||
and: [
|
||||
{
|
||||
parent: {
|
||||
equals: id,
|
||||
},
|
||||
],
|
||||
},
|
||||
extractAccessFromPermission(docPermissions.readVersions),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -252,23 +242,20 @@ export const getVersions = async ({
|
||||
depth: 0,
|
||||
global: globalConfig.slug,
|
||||
user,
|
||||
where: combineQueries(
|
||||
{
|
||||
and: [
|
||||
{
|
||||
'version._status': {
|
||||
equals: 'draft',
|
||||
},
|
||||
where: {
|
||||
and: [
|
||||
{
|
||||
'version._status': {
|
||||
equals: 'draft',
|
||||
},
|
||||
{
|
||||
updatedAt: {
|
||||
greater_than: publishedDoc.updatedAt,
|
||||
},
|
||||
},
|
||||
{
|
||||
updatedAt: {
|
||||
greater_than: publishedDoc.updatedAt,
|
||||
},
|
||||
],
|
||||
},
|
||||
extractAccessFromPermission(docPermissions.readVersions),
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use client'
|
||||
import type { OnConfirm } from '@payloadcms/ui'
|
||||
|
||||
import { getTranslation } from '@payloadcms/translations'
|
||||
import {
|
||||
Button,
|
||||
ConfirmationModal,
|
||||
PopupList,
|
||||
toast,
|
||||
useConfig,
|
||||
useModal,
|
||||
useRouteTransition,
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import { formatAdminURL, requests } from '@payloadcms/ui/shared'
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import React, { Fragment, useCallback, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import type { Props } from './types.js'
|
||||
|
||||
@@ -74,21 +75,27 @@ const Restore: React.FC<Props> = ({
|
||||
})
|
||||
}
|
||||
|
||||
const handleRestore = useCallback(async () => {
|
||||
const res = await requests.post(fetchURL, {
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
},
|
||||
})
|
||||
const handleRestore: OnConfirm = useCallback(
|
||||
async ({ closeConfirmationModal, setConfirming }) => {
|
||||
const res = await requests.post(fetchURL, {
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
},
|
||||
})
|
||||
|
||||
if (res.status === 200) {
|
||||
const json = await res.json()
|
||||
toast.success(json.message)
|
||||
return startRouteTransition(() => router.push(redirectURL))
|
||||
} else {
|
||||
toast.error(t('version:problemRestoringVersion'))
|
||||
}
|
||||
}, [fetchURL, redirectURL, t, i18n, router, startRouteTransition])
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
|
||||
if (res.status === 200) {
|
||||
const json = await res.json()
|
||||
toast.success(json.message)
|
||||
startRouteTransition(() => router.push(redirectURL))
|
||||
} else {
|
||||
toast.error(t('version:problemRestoringVersion'))
|
||||
}
|
||||
},
|
||||
[fetchURL, redirectURL, t, i18n, router, startRouteTransition],
|
||||
)
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/payload-cloud",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "The official Payload Cloud plugin",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "payload",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
|
||||
"keywords": [
|
||||
"admin panel",
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { AccessResult } from '../config/types.js'
|
||||
import type { Permission } from './index.js'
|
||||
|
||||
export const extractAccessFromPermission = (hasPermission: boolean | Permission): AccessResult => {
|
||||
if (typeof hasPermission === 'boolean') {
|
||||
return hasPermission
|
||||
}
|
||||
|
||||
const { permission, where } = hasPermission
|
||||
if (!permission) {
|
||||
return false
|
||||
}
|
||||
if (where && typeof where === 'object') {
|
||||
return where
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { lt } from '@payloadcms/translations/languages/lt'
|
||||
@@ -1045,13 +1045,6 @@ export type SelectField = {
|
||||
*/
|
||||
enumName?: DBIdentifierName
|
||||
hasMany?: boolean
|
||||
/** Customize generated GraphQL and Typescript schema names.
|
||||
* By default, it is bound to the collection.
|
||||
*
|
||||
* This is useful if you would like to generate a top level type to share amongst collections/fields.
|
||||
* **Note**: Top level types can collide, ensure they are unique amongst collections, arrays, groups, blocks, tabs.
|
||||
*/
|
||||
interfaceName?: string
|
||||
options: Option[]
|
||||
type: 'select'
|
||||
} & (
|
||||
@@ -1069,7 +1062,7 @@ export type SelectField = {
|
||||
export type SelectFieldClient = {
|
||||
admin?: AdminClient & Pick<SelectField['admin'], 'isClearable' | 'isSortable'>
|
||||
} & FieldBaseClient &
|
||||
Pick<SelectField, 'hasMany' | 'interfaceName' | 'options' | 'type'>
|
||||
Pick<SelectField, 'hasMany' | 'options' | 'type'>
|
||||
|
||||
type SharedRelationshipProperties = {
|
||||
filterOptions?: FilterOptions
|
||||
@@ -1276,13 +1269,6 @@ export type RadioField = {
|
||||
* Customize the DB enum name
|
||||
*/
|
||||
enumName?: DBIdentifierName
|
||||
/** Customize generated GraphQL and Typescript schema names.
|
||||
* By default, it is bound to the collection.
|
||||
*
|
||||
* This is useful if you would like to generate a top level type to share amongst collections/fields.
|
||||
* **Note**: Top level types can collide, ensure they are unique amongst collections, arrays, groups, blocks, tabs.
|
||||
*/
|
||||
interfaceName?: string
|
||||
options: Option[]
|
||||
type: 'radio'
|
||||
validate?: RadioFieldValidation
|
||||
@@ -1291,7 +1277,7 @@ export type RadioField = {
|
||||
export type RadioFieldClient = {
|
||||
admin?: AdminClient & Pick<RadioField['admin'], 'layout'>
|
||||
} & FieldBaseClient &
|
||||
Pick<RadioField, 'interfaceName' | 'options' | 'type'>
|
||||
Pick<RadioField, 'options' | 'type'>
|
||||
|
||||
type BlockFields = {
|
||||
[key: string]: any
|
||||
|
||||
@@ -968,7 +968,6 @@ interface RequestContext {
|
||||
export interface DatabaseAdapter extends BaseDatabaseAdapter {}
|
||||
export type { Payload, RequestContext }
|
||||
export { executeAuthStrategies } from './auth/executeAuthStrategies.js'
|
||||
export { extractAccessFromPermission } from './auth/extractAccessFromPermission.js'
|
||||
export { getAccessResults } from './auth/getAccessResults.js'
|
||||
export { getFieldsToSign } from './auth/getFieldsToSign.js'
|
||||
export * from './auth/index.js'
|
||||
@@ -985,7 +984,6 @@ export { resetPasswordOperation } from './auth/operations/resetPassword.js'
|
||||
export { unlockOperation } from './auth/operations/unlock.js'
|
||||
export { verifyEmailOperation } from './auth/operations/verifyEmail.js'
|
||||
export { JWTAuthentication } from './auth/strategies/jwt.js'
|
||||
|
||||
export type {
|
||||
AuthStrategyFunction,
|
||||
AuthStrategyFunctionArgs,
|
||||
@@ -1006,8 +1004,8 @@ export type {
|
||||
} from './auth/types.js'
|
||||
|
||||
export { generateImportMap } from './bin/generateImportMap/index.js'
|
||||
export type { ImportMap } from './bin/generateImportMap/index.js'
|
||||
|
||||
export type { ImportMap } from './bin/generateImportMap/index.js'
|
||||
export { genImportMapIterateFields } from './bin/generateImportMap/iterateFields.js'
|
||||
|
||||
export {
|
||||
@@ -1054,6 +1052,7 @@ export type {
|
||||
TypeWithID,
|
||||
TypeWithTimestamps,
|
||||
} from './collections/config/types.js'
|
||||
|
||||
export { createDataloaderCacheKey, getDataLoader } from './collections/dataloader.js'
|
||||
export { countOperation } from './collections/operations/count.js'
|
||||
export { createOperation } from './collections/operations/create.js'
|
||||
@@ -1076,8 +1075,8 @@ export {
|
||||
serverOnlyConfigProperties,
|
||||
type UnsanitizedClientConfig,
|
||||
} from './config/client.js'
|
||||
|
||||
export { defaults } from './config/defaults.js'
|
||||
|
||||
export { sanitizeConfig } from './config/sanitize.js'
|
||||
export type * from './config/types.js'
|
||||
export { combineQueries } from './database/combineQueries.js'
|
||||
|
||||
@@ -63,7 +63,7 @@ export const checkDocumentLockStatus = async ({
|
||||
collection: 'payload-locked-documents',
|
||||
limit: 1,
|
||||
pagination: false,
|
||||
req,
|
||||
//req,
|
||||
sort: '-updatedAt',
|
||||
where: lockedDocumentQuery,
|
||||
})
|
||||
|
||||
@@ -496,14 +496,6 @@ export function fieldsToJSONSchema(
|
||||
enum: buildOptionEnums(field.options),
|
||||
}
|
||||
|
||||
if (field.interfaceName) {
|
||||
interfaceNameDefinitions.set(field.interfaceName, fieldSchema)
|
||||
|
||||
fieldSchema = {
|
||||
$ref: `#/definitions/${field.interfaceName}`,
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@@ -665,15 +657,6 @@ export function fieldsToJSONSchema(
|
||||
fieldSchema.enum = optionEnums
|
||||
}
|
||||
}
|
||||
|
||||
if (field.interfaceName) {
|
||||
interfaceNameDefinitions.set(field.interfaceName, fieldSchema)
|
||||
|
||||
fieldSchema = {
|
||||
$ref: `#/definitions/${field.interfaceName}`,
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
@@ -48,10 +48,7 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
|
||||
let docBeingAccessed: EntityDoc | Promise<EntityDoc | undefined> | undefined
|
||||
|
||||
async function getEntityDoc({
|
||||
operation,
|
||||
where,
|
||||
}: { operation?: AllOperations; where?: Where } = {}): Promise<EntityDoc | undefined> {
|
||||
async function getEntityDoc({ where }: { where?: Where } = {}): Promise<EntityDoc | undefined> {
|
||||
if (!entity.slug) {
|
||||
return undefined
|
||||
}
|
||||
@@ -69,28 +66,18 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
|
||||
if (type === 'collection' && id) {
|
||||
if (typeof where === 'object') {
|
||||
const options = {
|
||||
const paginatedRes = await payload.find({
|
||||
collection: entity.slug,
|
||||
depth: 0,
|
||||
fallbackLocale: null,
|
||||
limit: 1,
|
||||
locale,
|
||||
overrideAccess: true,
|
||||
req,
|
||||
}
|
||||
if (operation === 'readVersions') {
|
||||
const paginatedRes = await payload.findVersions({
|
||||
...options,
|
||||
where: combineQueries(where, { parent: { equals: id } }),
|
||||
})
|
||||
return paginatedRes?.docs?.[0] || undefined
|
||||
}
|
||||
|
||||
const paginatedRes = await payload.find({
|
||||
...options,
|
||||
pagination: false,
|
||||
req,
|
||||
where: combineQueries(where, { id: { equals: id } }),
|
||||
})
|
||||
|
||||
return paginatedRes?.docs?.[0] || undefined
|
||||
}
|
||||
|
||||
@@ -129,9 +116,7 @@ export async function getEntityPolicies<T extends Args>(args: T): Promise<Return
|
||||
if (typeof accessResult === 'object' && !disableWhere) {
|
||||
mutablePolicies[operation] = {
|
||||
permission:
|
||||
id || type === 'global'
|
||||
? !!(await getEntityDoc({ operation, where: accessResult }))
|
||||
: true,
|
||||
id || type === 'global' ? !!(await getEntityDoc({ where: accessResult })) : true,
|
||||
where: accessResult,
|
||||
}
|
||||
} else if (mutablePolicies[operation]?.permission !== false) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-cloud-storage",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "The official cloud storage plugin for Payload CMS",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-form-builder",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Form builder plugin for Payload CMS",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-multi-tenant",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Multi Tenant plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import type { Access, CollectionConfig } from 'payload'
|
||||
|
||||
import type { MultiTenantPluginConfig } from '../types.js'
|
||||
|
||||
@@ -34,14 +34,21 @@ export const addCollectionAccess = <ConfigType>({
|
||||
fieldName,
|
||||
userHasAccessToAllTenants,
|
||||
}: Args<ConfigType>): void => {
|
||||
collectionAccessKeys.forEach((key) => {
|
||||
if (!collection?.access) {
|
||||
collection.access = {}
|
||||
}
|
||||
collectionAccessKeys.reduce<{
|
||||
[key in (typeof collectionAccessKeys)[number]]?: Access
|
||||
}>((acc, key) => {
|
||||
if (!collection.access) {
|
||||
collection.access = {}
|
||||
return acc
|
||||
}
|
||||
collection.access[key] = withTenantAccess<ConfigType>({
|
||||
accessFunction: collection.access?.[key],
|
||||
fieldName: key === 'readVersions' ? `version.${fieldName}` : fieldName,
|
||||
fieldName,
|
||||
userHasAccessToAllTenants,
|
||||
})
|
||||
})
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-nested-docs",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "The official Nested Docs plugin for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-redirects",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Redirects plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-search",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Search plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import type { OnConfirm } from '@payloadcms/ui'
|
||||
|
||||
import {
|
||||
ConfirmationModal,
|
||||
Popup,
|
||||
@@ -24,7 +26,7 @@ export const ReindexButtonClient: React.FC<ReindexButtonProps> = ({
|
||||
searchCollections,
|
||||
searchSlug,
|
||||
}) => {
|
||||
const { openModal } = useModal()
|
||||
const { closeModal, openModal } = useModal()
|
||||
|
||||
const { config } = useConfig()
|
||||
|
||||
@@ -39,33 +41,46 @@ export const ReindexButtonClient: React.FC<ReindexButtonProps> = ({
|
||||
|
||||
const openConfirmModal = useCallback(() => openModal(confirmReindexModalSlug), [openModal])
|
||||
|
||||
const handleReindexSubmit = useCallback(async () => {
|
||||
if (!reindexCollections.length) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${config.routes.api}/${searchSlug}/reindex?locale=${locale.code}`, {
|
||||
body: JSON.stringify({
|
||||
collections: reindexCollections,
|
||||
}),
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
const { message } = (await res.json()) as { message: string }
|
||||
|
||||
if (!res.ok) {
|
||||
toast.error(message)
|
||||
} else {
|
||||
toast.success(message)
|
||||
return router.refresh()
|
||||
const handleReindexSubmit: OnConfirm = useCallback(
|
||||
async ({ closeConfirmationModal, setConfirming }) => {
|
||||
if (!reindexCollections.length) {
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
return
|
||||
}
|
||||
} catch (_err: unknown) {
|
||||
// swallow error, toast shown above
|
||||
} finally {
|
||||
setReindexCollections([])
|
||||
}
|
||||
}, [reindexCollections, router, searchSlug, locale, config])
|
||||
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${config.routes.api}/${searchSlug}/reindex?locale=${locale.code}`,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
collections: reindexCollections,
|
||||
}),
|
||||
method: 'POST',
|
||||
},
|
||||
)
|
||||
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
|
||||
const { message } = (await res.json()) as { message: string }
|
||||
|
||||
if (!res.ok) {
|
||||
toast.error(message)
|
||||
} else {
|
||||
toast.success(message)
|
||||
router.refresh()
|
||||
}
|
||||
} catch (_err: unknown) {
|
||||
// swallow error, toast shown above
|
||||
} finally {
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
setReindexCollections([])
|
||||
}
|
||||
},
|
||||
[reindexCollections, router, searchSlug, locale, config],
|
||||
)
|
||||
|
||||
const handleShowConfirmModal = useCallback(
|
||||
(collections: string | string[] = searchCollections) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-sentry",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Sentry plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-seo",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "SEO plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -18,7 +18,6 @@ import { hu } from './hu.js'
|
||||
import { it } from './it.js'
|
||||
import { ja } from './ja.js'
|
||||
import { ko } from './ko.js'
|
||||
import { lt } from './lt.js'
|
||||
import { my } from './my.js'
|
||||
import { nb } from './nb.js'
|
||||
import { nl } from './nl.js'
|
||||
@@ -57,7 +56,6 @@ export const translations = {
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
lt,
|
||||
my,
|
||||
nb,
|
||||
nl,
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import type { GenericTranslationsObject } from '@payloadcms/translations'
|
||||
|
||||
export const lt: GenericTranslationsObject = {
|
||||
$schema: './translation-schema.json',
|
||||
'plugin-seo': {
|
||||
almostThere: 'Beveik baigta',
|
||||
autoGenerate: 'Automatinis generavimas',
|
||||
bestPractices: 'geriausios praktikos',
|
||||
characterCount: '{{current}}/{{minLength}}-{{maxLength}} simbolių, ',
|
||||
charactersLeftOver: '{{characters}} likusių simbolių',
|
||||
charactersToGo: '{{characters}} simbolių liko',
|
||||
charactersTooMany: '{{characters}} per daug simbolių',
|
||||
checksPassing: '{{current}}/{{max}} tikrinimų sėkmingi',
|
||||
good: 'Gerai',
|
||||
imageAutoGenerationTip: 'Automatinis generavimas paims pasirinktą pagrindinį vaizdą.',
|
||||
lengthTipDescription:
|
||||
'Šis tekstas turi būti tarp {{minLength}} ir {{maxLength}} simbolių. Norėdami gauti pagalbos rašant kokybiškus meta aprašus, žiūrėkite ',
|
||||
lengthTipTitle:
|
||||
'Šis tekstas turi būti tarp {{minLength}} ir {{maxLength}} simbolių. Norėdami gauti pagalbos rašant kokybiškus meta pavadinimus, žiūrėkite ',
|
||||
missing: 'Trūksta',
|
||||
noImage: 'Nėra vaizdo',
|
||||
preview: 'Peržiūra',
|
||||
previewDescription:
|
||||
'Tikrųjų paieškos rezultatų gali skirtis priklausomai nuo turinio ir paieškos svarbos.',
|
||||
tooLong: 'Per ilgas',
|
||||
tooShort: 'Per trumpas',
|
||||
},
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/plugin-stripe",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Stripe plugin for Payload",
|
||||
"keywords": [
|
||||
"payload",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-lexical",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "The officially supported Lexical richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
@@ -354,8 +354,8 @@
|
||||
"@lexical/react": "0.21.0",
|
||||
"@lexical/rich-text": "0.21.0",
|
||||
"@lexical/selection": "0.21.0",
|
||||
"@lexical/table": "0.21.0",
|
||||
"@lexical/utils": "0.21.0",
|
||||
"@lexical/table": "0.21.0",
|
||||
"@payloadcms/translations": "workspace:*",
|
||||
"@payloadcms/ui": "workspace:*",
|
||||
"@types/uuid": "10.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/richtext-slate",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "The officially supported Slate richtext adapter for Payload",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-azure",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Payload storage adapter for Azure Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-gcs",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Payload storage adapter for Google Cloud Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-s3",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Payload storage adapter for Amazon S3",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-uploadthing",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Payload storage adapter for uploadthing",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/storage-vercel-blob",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"description": "Payload storage adapter for Vercel Blob Storage",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/translations",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -18,7 +18,6 @@ import { hu } from '../languages/hu.js'
|
||||
import { it } from '../languages/it.js'
|
||||
import { ja } from '../languages/ja.js'
|
||||
import { ko } from '../languages/ko.js'
|
||||
import { lt } from '../languages/lt.js'
|
||||
import { my } from '../languages/my.js'
|
||||
import { nb } from '../languages/nb.js'
|
||||
import { nl } from '../languages/nl.js'
|
||||
@@ -57,7 +56,6 @@ export const translations = {
|
||||
it,
|
||||
ja,
|
||||
ko,
|
||||
lt,
|
||||
my,
|
||||
nb,
|
||||
nl,
|
||||
|
||||
@@ -75,10 +75,6 @@ export const importDateFNSLocale = async (locale: string): Promise<Locale> => {
|
||||
case 'ko':
|
||||
result = (await import('date-fns/locale/ko')).ko
|
||||
|
||||
break
|
||||
case 'lt':
|
||||
result = (await import('date-fns/locale/lt')).lt
|
||||
|
||||
break
|
||||
case 'nb':
|
||||
result = (await import('date-fns/locale/nb')).nb
|
||||
|
||||
@@ -1,513 +0,0 @@
|
||||
import type { DefaultTranslationsObject, Language } from '../types.js'
|
||||
|
||||
export const ltTranslations: DefaultTranslationsObject = {
|
||||
authentication: {
|
||||
account: 'Paskyra',
|
||||
accountOfCurrentUser: 'Dabartinio vartotojo paskyra',
|
||||
accountVerified: 'Sąskaita sėkmingai patvirtinta.',
|
||||
alreadyActivated: 'Jau aktyvuota',
|
||||
alreadyLoggedIn: 'Jau prisijungęs',
|
||||
apiKey: 'API raktas',
|
||||
authenticated: 'Autentifikuotas',
|
||||
backToLogin: 'Grįžti į prisijungimą',
|
||||
beginCreateFirstUser: 'Pradėkite, sukurdami savo pirmąjį vartotoją.',
|
||||
changePassword: 'Keisti slaptažodį',
|
||||
checkYourEmailForPasswordReset:
|
||||
'Jei šis el. pašto adresas yra susijęs su paskyra, netrukus gausite instrukcijas, kaip atstatyti savo slaptažodį. Jei laiško nesimate savo gautiesiųjų dėžutėje, patikrinkite savo šlamšto ar nereikalingų laiškų aplanką.',
|
||||
confirmGeneration: 'Patvirtinkite generavimą',
|
||||
confirmPassword: 'Patvirtinkite slaptažodį',
|
||||
createFirstUser: 'Sukurkite pirmąjį vartotoją',
|
||||
emailNotValid: 'Pateiktas el. paštas negalioja',
|
||||
emailOrUsername: 'El. paštas arba vartotojo vardas',
|
||||
emailSent: 'El. paštas išsiųstas',
|
||||
emailVerified: 'El. paštas sėkmingai patvirtintas.',
|
||||
enableAPIKey: 'Įgalinti API raktą',
|
||||
failedToUnlock: 'Nepavyko atrakinti',
|
||||
forceUnlock: 'Priverstinis atrakinimas',
|
||||
forgotPassword: 'Pamiršote slaptažodį',
|
||||
forgotPasswordEmailInstructions:
|
||||
'Prašome įvesti savo el. paštą žemiau. Gausite el. laišką su instrukcijomis, kaip atstatyti savo slaptažodį.',
|
||||
forgotPasswordQuestion: 'Pamiršote slaptažodį?',
|
||||
forgotPasswordUsernameInstructions:
|
||||
'Prašome įvesti savo vartotojo vardą žemiau. Instrukcijos, kaip atstatyti slaptažodį, bus išsiųstos į el. pašto adresą, susietą su jūsų vartotojo vardu.',
|
||||
generate: 'Generuoti',
|
||||
generateNewAPIKey: 'Sukurkite naują API raktą',
|
||||
generatingNewAPIKeyWillInvalidate:
|
||||
'Sugeneruojant naują API raktą, bus <1>anuliuotas</1> ankstesnis raktas. Ar tikrai norite tęsti?',
|
||||
lockUntil: 'Užrakinti iki',
|
||||
logBackIn: 'Prisijunkite vėl',
|
||||
loggedIn: 'Norėdami prisijungti kitu vartotoju, turėtumėte iš pradžių <0>atsijungti</0>.',
|
||||
loggedInChangePassword:
|
||||
'Norėdami pakeisti slaptažodį, eikite į savo <0>paskyrą</0> ir ten redaguokite savo slaptažodį.',
|
||||
loggedOutInactivity: 'Jūs buvote atjungtas dėl neveiklumo.',
|
||||
loggedOutSuccessfully: 'Sėkmingai atsijungėte.',
|
||||
loggingOut: 'Atsijungimas...',
|
||||
login: 'Prisijungti',
|
||||
loginAttempts: 'Prisijungimo bandymai',
|
||||
loginUser: 'Prisijungti vartotojui',
|
||||
loginWithAnotherUser:
|
||||
'Norėdami prisijungti su kitu vartotoju, turėtumėte iš pradžių <0>atsijungti</0>.',
|
||||
logOut: 'Atsijungti',
|
||||
logout: 'Atsijungti',
|
||||
logoutSuccessful: 'Sėkmingai atsijungta.',
|
||||
logoutUser: 'Atjungti vartotoją',
|
||||
newAccountCreated:
|
||||
'Jums ką tik buvo sukurta nauja paskyra, kad galėtumėte prisijungti prie <a href="{{serverURL}}">{{serverURL}}</a> Prašome paspausti ant šios nuorodos arba įklijuoti apačioje esantį URL į savo naršyklę, kad patvirtintumėte savo el. pašto adresą: <a href="{{verificationURL}}">{{verificationURL}}</a><br> Patvirtinę savo el. pašto adresą, sėkmingai galėsite prisijungti.',
|
||||
newAPIKeyGenerated: 'Sugeneruotas naujas API raktas.',
|
||||
newPassword: 'Naujas slaptažodis',
|
||||
passed: 'Autentifikacija sėkminga',
|
||||
passwordResetSuccessfully: 'Slaptažodis sėkmingai atnaujintas.',
|
||||
resetPassword: 'Atstatyti slaptažodį',
|
||||
resetPasswordExpiration: 'Atstatyti slaptažodžio galiojimo laiką',
|
||||
resetPasswordToken: 'Slaptažodžio atkūrimo žetonas',
|
||||
resetYourPassword: 'Atstatykite savo slaptažodį',
|
||||
stayLoggedIn: 'Likite prisijungę',
|
||||
successfullyRegisteredFirstUser: 'Sėkmingai užregistruotas pirmas vartotojas.',
|
||||
successfullyUnlocked: 'Sėkmingai atrakinta',
|
||||
tokenRefreshSuccessful: 'Žetonų atnaujinimas sėkmingas.',
|
||||
unableToVerify: 'Negalima patikrinti',
|
||||
username: 'Vartotojo vardas',
|
||||
usernameNotValid: 'Pateiktas vartotojo vardas yra netinkamas',
|
||||
verified: 'Patvirtinta',
|
||||
verifiedSuccessfully: 'Sėkmingai patvirtinta',
|
||||
verify: 'Patikrinkite',
|
||||
verifyUser: 'Patvirtinti vartotoją',
|
||||
verifyYourEmail: 'Patvirtinkite savo el. paštą',
|
||||
youAreInactive:
|
||||
'Jūs kurį laiką neveikėte ir netrukus būsite automatiškai atjungtas dėl jūsų pačių saugumo. Ar norėtumėte likti prisijungęs?',
|
||||
youAreReceivingResetPassword:
|
||||
'Gavote šį pranešimą, nes jūs (arba kažkas kitas) paprašėte atstatyti slaptažodį savo paskyrai. Norėdami užbaigti procesą, spustelėkite šią nuorodą arba įklijuokite ją į savo naršyklę:',
|
||||
youDidNotRequestPassword:
|
||||
'Jei to neprašėte, prašome ignoruoti šį el. laišką ir jūsų slaptažodis išliks nepakeistas.',
|
||||
},
|
||||
error: {
|
||||
accountAlreadyActivated: 'Ši paskyra jau aktyvuota.',
|
||||
autosaving: 'Šio dokumento automatinio išsaugojimo metu kilo problema.',
|
||||
correctInvalidFields: 'Prašome ištaisyti neteisingus laukus.',
|
||||
deletingFile: 'Įvyko klaida trinant failą.',
|
||||
deletingTitle:
|
||||
'Įvyko klaida bandant ištrinti {{title}}. Patikrinkite savo ryšį ir bandykite dar kartą.',
|
||||
emailOrPasswordIncorrect: 'Pateiktas el. pašto adresas arba slaptažodis yra neteisingi.',
|
||||
followingFieldsInvalid_one: 'Šis laukas yra netinkamas:',
|
||||
followingFieldsInvalid_other: 'Šie laukai yra neteisingi:',
|
||||
incorrectCollection: 'Neteisinga kolekcija',
|
||||
invalidFileType: 'Netinkamas failo tipas',
|
||||
invalidFileTypeValue: 'Neteisingas failo tipas: {{value}}',
|
||||
invalidRequestArgs: 'Netinkami argumentai perduoti užklausoje: {{args}}',
|
||||
loadingDocument: 'Įvyko klaida įkeliant dokumentą, kurio ID yra {{id}}.',
|
||||
localesNotSaved_one: 'Negalima išsaugoti šios lokalės:',
|
||||
localesNotSaved_other: 'Šios lokalės negalėjo būti išsaugotos:',
|
||||
logoutFailed: 'Atsijungimas nepavyko.',
|
||||
missingEmail: 'Trūksta el. pašto.',
|
||||
missingIDOfDocument: 'Trūksta dokumento, kurį reikia atnaujinti, ID.',
|
||||
missingIDOfVersion: 'Trūksta versijos ID.',
|
||||
missingRequiredData: 'Trūksta reikalingų duomenų.',
|
||||
noFilesUploaded: 'Neįkelta jokių failų.',
|
||||
noMatchedField: 'Nerasta atitinkamo lauko „{{label}}“',
|
||||
notAllowedToAccessPage: 'Jums neleidžiama prieiti prie šio puslapio.',
|
||||
notAllowedToPerformAction: 'Jums neleidžiama atlikti šio veiksmo.',
|
||||
notFound: 'Pageidaujamas išteklius nerasta.',
|
||||
noUser: 'Nėra vartotojo',
|
||||
previewing: 'Šiam dokumentui peržiūrėti kilo problema.',
|
||||
problemUploadingFile: 'Failo įkelti nepavyko dėl problemos.',
|
||||
tokenInvalidOrExpired: 'Žetonas yra neteisingas arba jo galiojimas pasibaigė.',
|
||||
tokenNotProvided: 'Žetonas nesuteiktas.',
|
||||
unableToDeleteCount: 'Negalima ištrinti {{count}} iš {{total}} {{label}}.',
|
||||
unableToReindexCollection:
|
||||
'Klaida perindeksuojant rinkinį {{collection}}. Operacija nutraukta.',
|
||||
unableToUpdateCount: 'Nepavyko atnaujinti {{count}} iš {{total}} {{label}}.',
|
||||
unauthorized: 'Neleistina, turite būti prisijungęs, kad galėtumėte teikti šį prašymą.',
|
||||
unauthorizedAdmin:
|
||||
'Neleidžiama, šis vartotojas neturi prieigos prie administratoriaus panelės.',
|
||||
unknown: 'Įvyko nežinoma klaida.',
|
||||
unPublishingDocument: 'Šio dokumento nepublikuojant kildavo problema.',
|
||||
unspecific: 'Įvyko klaida.',
|
||||
userEmailAlreadyRegistered: 'Vartotojas su nurodytu el. paštu jau yra užregistruotas.',
|
||||
userLocked: 'Šis vartotojas užrakintas dėl per daug nepavykusių prisijungimo bandymų.',
|
||||
usernameAlreadyRegistered: 'Vartotojas su nurodytu vartotojo vardu jau užregistruotas.',
|
||||
usernameOrPasswordIncorrect: 'Pateiktas vartotojo vardas arba slaptažodis yra neteisingas.',
|
||||
valueMustBeUnique: 'Vertė turi būti unikalu.',
|
||||
verificationTokenInvalid: 'Patvirtinimo kodas yra negaliojantis.',
|
||||
},
|
||||
fields: {
|
||||
addLabel: 'Pridėkite {{žymė}}',
|
||||
addLink: 'Pridėti nuorodą',
|
||||
addNew: 'Pridėti naują',
|
||||
addNewLabel: 'Pridėti naują {{žymę}}',
|
||||
addRelationship: 'Pridėti santykį',
|
||||
addUpload: 'Pridėti Įkelti',
|
||||
block: 'blokas',
|
||||
blocks: 'blokai',
|
||||
blockType: 'Blokas Tipas',
|
||||
chooseBetweenCustomTextOrDocument:
|
||||
'Pasirinkite tarp pasirinkimo įvesti tinkintą tekstą URL arba nuorodos į kitą dokumentą.',
|
||||
chooseDocumentToLink: 'Pasirinkite dokumentą, prie kurio norite prisegti.',
|
||||
chooseFromExisting: 'Pasirinkite iš esamų',
|
||||
chooseLabel: 'Pasirinkite {{žymė}}',
|
||||
collapseAll: 'Sutraukti viską',
|
||||
customURL: 'Pasirinktinis URL',
|
||||
editLabelData: 'Redaguoti {{label}} duomenis',
|
||||
editLink: 'Redaguoti nuorodą',
|
||||
editRelationship: 'Redaguoti santykius',
|
||||
enterURL: 'Įveskite URL',
|
||||
internalLink: 'Vidinis nuorodos',
|
||||
itemsAndMore: '{{items}} ir dar {{count}}',
|
||||
labelRelationship: '{{label}} Santykiai',
|
||||
latitude: 'Platuma',
|
||||
linkedTo: 'Susijęs su <0>{{label}}</0>',
|
||||
linkType: 'Nuorodos tipas',
|
||||
longitude: 'Ilgumažė',
|
||||
newLabel: 'Naujas {{žymė}}',
|
||||
openInNewTab: 'Atidaryti naujame skirtuke',
|
||||
passwordsDoNotMatch: 'Slaptažodžiai nesutampa.',
|
||||
relatedDocument: 'Susijęs dokumentas',
|
||||
relationTo: 'Santykis su',
|
||||
removeRelationship: 'Pašalinti ryšį',
|
||||
removeUpload: 'Pašalinti įkėlimą',
|
||||
saveChanges: 'Išsaugoti pakeitimus',
|
||||
searchForBlock: 'Ieškokite bloko',
|
||||
selectExistingLabel: 'Pasirinkite esamą {{žymę}}',
|
||||
selectFieldsToEdit: 'Pasirinkite laukus, kuriuos norite redaguoti',
|
||||
showAll: 'Rodyti viską',
|
||||
swapRelationship: 'Apkeičiamas santykis',
|
||||
swapUpload: 'Keitimo įkėlimas',
|
||||
textToDisplay: 'Rodyti tekstą',
|
||||
toggleBlock: 'Perjungti bloką',
|
||||
uploadNewLabel: 'Įkelti naują {{label}}',
|
||||
},
|
||||
general: {
|
||||
aboutToDelete: 'Jūs ketinate ištrinti {{label}} <1>{{title}}</1>. Ar esate tikri?',
|
||||
aboutToDeleteCount_many: 'Jūs ketinate ištrinti {{count}} {{label}}',
|
||||
aboutToDeleteCount_one: 'Jūs ketinate ištrinti {{count}} {{label}}',
|
||||
aboutToDeleteCount_other: 'Jūs ketinate ištrinti {{count}} {{label}}',
|
||||
addBelow: 'Pridėti žemiau',
|
||||
addFilter: 'Pridėti filtrą',
|
||||
adminTheme: 'Admin temos',
|
||||
all: 'Visi',
|
||||
allCollections: 'Visos kolekcijos',
|
||||
and: 'Ir',
|
||||
anotherUser: 'Kitas vartotojas',
|
||||
anotherUserTakenOver: 'Kitas naudotojas perėmė šio dokumento redagavimą.',
|
||||
applyChanges: 'Taikyti pakeitimus',
|
||||
ascending: 'Kylantis',
|
||||
automatic: 'Automatinis',
|
||||
backToDashboard: 'Atgal į informacinę skydelį',
|
||||
cancel: 'Atšaukti',
|
||||
changesNotSaved:
|
||||
'Jūsų pakeitimai nebuvo išsaugoti. Jei dabar išeisite, prarasite savo pakeitimus.',
|
||||
clearAll: 'Išvalyti viską',
|
||||
close: 'Uždaryti',
|
||||
collapse: 'Susikolimas',
|
||||
collections: 'Kolekcijos',
|
||||
columns: 'Stulpeliai',
|
||||
columnToSort: 'Rūšiuoti stulpelį',
|
||||
confirm: 'Patvirtinti',
|
||||
confirmCopy: 'Patvirtinkite kopiją',
|
||||
confirmDeletion: 'Patvirtinkite šalinimą',
|
||||
confirmDuplication: 'Patvirtinkite dubliavimą',
|
||||
confirmReindex: 'Perindeksuoti visas {{kolekcijas}}?',
|
||||
confirmReindexAll: 'Perindeksuoti visas kolekcijas?',
|
||||
confirmReindexDescription:
|
||||
'Tai pašalins esamus indeksus ir iš naujo indeksuos dokumentus kolekcijose {{collections}}.',
|
||||
confirmReindexDescriptionAll:
|
||||
'Tai pašalins esamas indeksus ir perindeksuos dokumentus visose kolekcijose.',
|
||||
copied: 'Nukopijuota',
|
||||
copy: 'Kopijuoti',
|
||||
copying: 'Kopijavimas',
|
||||
copyWarning:
|
||||
'Jūs ketinate perrašyti {{to}} į {{from}} šildymui {{label}} {{title}}. Ar esate tikri?',
|
||||
create: 'Sukurti',
|
||||
created: 'Sukurta',
|
||||
createdAt: 'Sukurta',
|
||||
createNew: 'Sukurti naują',
|
||||
createNewLabel: 'Sukurti naują {{label}}',
|
||||
creating: 'Kuriant',
|
||||
creatingNewLabel: 'Kuriamas naujas {{label}}',
|
||||
currentlyEditing:
|
||||
'šiuo metu redaguoja šį dokumentą. Jei perimsite, jie bus užblokuoti ir negalės toliau redaguoti, o taip pat gali prarasti neišsaugotus pakeitimus.',
|
||||
custom: 'Paprastas',
|
||||
dark: 'Tamsus',
|
||||
dashboard: 'Prietaisų skydelis',
|
||||
delete: 'Ištrinti',
|
||||
deletedCountSuccessfully: 'Sėkmingai ištrinta {{count}} {{label}}.',
|
||||
deletedSuccessfully: 'Sėkmingai ištrinta.',
|
||||
deleting: 'Trinama...',
|
||||
depth: 'Gylis',
|
||||
descending: 'Mažėjantis',
|
||||
deselectAllRows: 'Atžymėkite visas eilutes',
|
||||
document: 'Dokumentas',
|
||||
documentLocked: 'Dokumentas užrakintas',
|
||||
documents: 'Dokumentai',
|
||||
duplicate: 'Dublikatas',
|
||||
duplicateWithoutSaving: 'Dubliuoti be įrašytų pakeitimų',
|
||||
edit: 'Redaguoti',
|
||||
editAll: 'Redaguoti viską',
|
||||
editedSince: 'Redaguota nuo',
|
||||
editing: 'Redagavimas',
|
||||
editingLabel_many: 'Redaguojama {{count}} {{label}}',
|
||||
editingLabel_one: 'Redaguojama {{count}} {{label}}',
|
||||
editingLabel_other: 'Redaguojamas {{count}} {{label}}',
|
||||
editingTakenOver: 'Redagavimas perimtas',
|
||||
editLabel: 'Redaguoti {{žymę}}',
|
||||
email: 'El. paštas',
|
||||
emailAddress: 'El. pašto adresas',
|
||||
enterAValue: 'Įveskite reikšmę',
|
||||
error: 'Klaida',
|
||||
errors: 'Klaidos',
|
||||
fallbackToDefaultLocale: 'Grįžkite į numatytąją vietovę',
|
||||
false: 'Netiesa',
|
||||
filter: 'Filtruoti',
|
||||
filters: 'Filtrai',
|
||||
filterWhere: 'Filtruoti {{label}}, kur',
|
||||
globals: 'Globalai',
|
||||
goBack: 'Grįžkite',
|
||||
isEditing: 'redaguoja',
|
||||
language: 'Kalba',
|
||||
lastModified: 'Paskutinį kartą modifikuota',
|
||||
leaveAnyway: 'Vis tiek išeikite',
|
||||
leaveWithoutSaving: 'Išeikite neišsaugoję',
|
||||
light: 'Šviesa',
|
||||
livePreview: 'Tiesioginė peržiūra',
|
||||
loading: 'Kraunama',
|
||||
locale: 'Lokalė',
|
||||
locales: 'Lokalės',
|
||||
menu: 'Meniu',
|
||||
moreOptions: 'Daugiau parinkčių',
|
||||
moveDown: 'Perkelti žemyn',
|
||||
moveUp: 'Pakilti',
|
||||
newPassword: 'Naujas slaptažodis',
|
||||
next: 'Toliau',
|
||||
noDateSelected: 'Pasirinktos datos nėra',
|
||||
noFiltersSet: 'Nenustatyti jokie filtrai',
|
||||
noLabel: '<Ne {{label}}>',
|
||||
none: 'Jokios',
|
||||
noOptions: 'Jokių variantų',
|
||||
noResults:
|
||||
'Nerasta jokių {{label}}. Arba dar nėra sukurtų {{label}}, arba jie neatitinka nurodytų filtrų aukščiau.',
|
||||
notFound: 'Nerasta',
|
||||
nothingFound: 'Nieko nerasta',
|
||||
noUpcomingEventsScheduled: 'Nėra suplanuotų būsimų renginių.',
|
||||
noValue: 'Nėra vertės',
|
||||
of: 'apie',
|
||||
only: 'Tik',
|
||||
open: 'Atidaryti',
|
||||
or: 'Arba',
|
||||
order: 'Užsakyti',
|
||||
overwriteExistingData: 'Perrašyti esamus lauko duomenis',
|
||||
pageNotFound: 'Puslapis nerastas',
|
||||
password: 'Slaptažodis',
|
||||
payloadSettings: 'Payload nustatymai',
|
||||
perPage: 'Puslapyje: {{limit}}',
|
||||
previous: 'Ankstesnis',
|
||||
reindex: 'Perindeksuoti',
|
||||
reindexingAll: 'Perindeksuojamos visos {{kolekcijos}}.',
|
||||
remove: 'Pašalinti',
|
||||
reset: 'Atstatyti',
|
||||
resetPreferences: 'Atstatyti nuostatas',
|
||||
resetPreferencesDescription: 'Tai atstatys visas jūsų nuostatas į numatytąsias reikšmes.',
|
||||
resettingPreferences: 'Nustatymų atstatymas.',
|
||||
row: 'Eilutė',
|
||||
rows: 'Eilutės',
|
||||
save: 'Išsaugoti',
|
||||
saving: 'Išsaugoti...',
|
||||
schedulePublishFor: 'Suplanuokite publikaciją „{{title}}“',
|
||||
searchBy: 'Ieškokite pagal {{žymę}}',
|
||||
selectAll: 'Pasirinkite visus {{count}} {{label}}',
|
||||
selectAllRows: 'Pasirinkite visas eilutes',
|
||||
selectedCount: '{{count}} {{label}} pasirinkta',
|
||||
selectValue: 'Pasirinkite reikšmę',
|
||||
showAllLabel: 'Rodyti visus {{label}}',
|
||||
sorryNotFound: 'Atsiprašau - nėra nieko, atitinkančio jūsų užklausą.',
|
||||
sort: 'Rūšiuoti',
|
||||
sortByLabelDirection: 'Rūšiuoti pagal {{label}} {{direction}}',
|
||||
stayOnThisPage: 'Likite šiame puslapyje',
|
||||
submissionSuccessful: 'Pateikimas sėkmingas.',
|
||||
submit: 'Pateikti',
|
||||
submitting: 'Pateikiama...',
|
||||
success: 'Sėkmė',
|
||||
successfullyCreated: '{{label}} sėkmingai sukurtas.',
|
||||
successfullyDuplicated: '{{label}} sėkmingai dubliuotas.',
|
||||
successfullyReindexed:
|
||||
'Sėkmingai perindeksuota {{count}} iš {{total}} dokumentų iš {{collections}}',
|
||||
takeOver: 'Perimti',
|
||||
thisLanguage: 'Lietuvių',
|
||||
time: 'Laikas',
|
||||
timezone: 'Laiko juosta',
|
||||
titleDeleted: '{{label}} "{{title}}" sėkmingai ištrinta.',
|
||||
true: 'Tiesa',
|
||||
unauthorized: 'Neleistinas',
|
||||
unsavedChanges: 'Turite neišsaugotų pakeitimų. Išsaugokite arba atmestkite prieš tęsdami.',
|
||||
unsavedChangesDuplicate: 'Jūs turite neišsaugotų pakeitimų. Ar norėtumėte tęsti dubliavimą?',
|
||||
untitled: 'Neužpavadinamas',
|
||||
upcomingEvents: 'Artimieji renginiai',
|
||||
updatedAt: 'Atnaujinta',
|
||||
updatedCountSuccessfully: '{{count}} {{label}} sėkmingai atnaujinta.',
|
||||
updatedSuccessfully: 'Sėkmingai atnaujinta.',
|
||||
updating: 'Atnaujinimas',
|
||||
uploading: 'Įkeliama',
|
||||
uploadingBulk: 'Įkeliamas {{current}} iš {{total}}',
|
||||
user: 'Vartotojas',
|
||||
username: 'Vartotojo vardas',
|
||||
users: 'Vartotojai',
|
||||
value: 'Vertė',
|
||||
viewReadOnly: 'Peržiūrėti tik skaitymui',
|
||||
welcome: 'Sveiki',
|
||||
},
|
||||
localization: {
|
||||
cannotCopySameLocale: 'Negalima kopijuoti į tą pačią vietovę',
|
||||
copyFrom: 'Kopijuoti iš',
|
||||
copyFromTo: 'Kopijavimas iš {{from}} į {{to}}',
|
||||
copyTo: 'Kopijuoti į',
|
||||
copyToLocale: 'Kopijuoti į vietovę',
|
||||
localeToPublish: 'Publikuoti lokacijoje',
|
||||
selectLocaleToCopy: 'Pasirinkite lokalės kopijavimui',
|
||||
},
|
||||
operators: {
|
||||
contains: 'yra',
|
||||
equals: 'lygus',
|
||||
exists: 'egzistuoja',
|
||||
intersects: 'susikerta',
|
||||
isGreaterThan: 'yra didesnis nei',
|
||||
isGreaterThanOrEqualTo: 'yra didesnis arba lygus',
|
||||
isIn: 'yra',
|
||||
isLessThan: 'yra mažiau nei',
|
||||
isLessThanOrEqualTo: 'yra mažiau arba lygu',
|
||||
isLike: 'yra panašu',
|
||||
isNotEqualTo: 'nelygu',
|
||||
isNotIn: 'nėra',
|
||||
near: 'šalia',
|
||||
within: 'viduje',
|
||||
},
|
||||
upload: {
|
||||
addFile: 'Pridėti failą',
|
||||
addFiles: 'Pridėti failus',
|
||||
bulkUpload: 'Masinis įkėlimas',
|
||||
crop: 'Pasėlis',
|
||||
cropToolDescription:
|
||||
'Temkite pasirinktos srities kampus, nubrėžkite naują sritį arba koreguokite žemiau esančias reikšmes.',
|
||||
dragAndDrop: 'Temkite ir numeskite failą',
|
||||
dragAndDropHere: 'arba nuvilkite failą čia',
|
||||
editImage: 'Redaguoti vaizdą',
|
||||
fileName: 'Failo pavadinimas',
|
||||
fileSize: 'Failo dydis',
|
||||
filesToUpload: 'Įkelti failai',
|
||||
fileToUpload: 'Įkelti failą',
|
||||
focalPoint: 'Fokuso Taškas',
|
||||
focalPointDescription:
|
||||
'Temkite fokusavimo tašką tiesiogiai peržiūroje arba reguliuokite žemiau esančias reikšmes.',
|
||||
height: 'Aukštis',
|
||||
lessInfo: 'Mažiau informacijos',
|
||||
moreInfo: 'Daugiau informacijos',
|
||||
pasteURL: 'Įklijuokite URL',
|
||||
previewSizes: 'Peržiūros dydžiai',
|
||||
selectCollectionToBrowse: 'Pasirinkite kolekciją, kurią norėtumėte naršyti',
|
||||
selectFile: 'Pasirinkite failą',
|
||||
setCropArea: 'Nustatykite pjovimo plotą',
|
||||
setFocalPoint: 'Nustatyti fokuso tašką',
|
||||
sizes: 'Dydžiai',
|
||||
sizesFor: 'Dydžiai skirti {{žymei}}',
|
||||
width: 'Plotis',
|
||||
},
|
||||
validation: {
|
||||
emailAddress: 'Įveskite galiojantį el. pašto adresą.',
|
||||
enterNumber: 'Įveskite galiojantį skaičių.',
|
||||
fieldHasNo: 'Šiame lauke nėra {{label}}',
|
||||
greaterThanMax:
|
||||
'{{value}} yra didesnė nei leidžiama maksimali {{label}} reikšmė, kuri yra {{max}}.',
|
||||
invalidInput: 'Šis laukas turi netinkamą įvestį.',
|
||||
invalidSelection: 'Šiame lauke yra netinkamas pasirinkimas.',
|
||||
invalidSelections: 'Šiame lauke yra šios netinkamos parinktys:',
|
||||
lessThanMin:
|
||||
'{{value}} yra mažesnė nei leidžiama minimali {{label}} reikšmė, kuri yra {{min}}.',
|
||||
limitReached: 'Pasiektas limitas, galima pridėti tik {{max}} daiktus.',
|
||||
longerThanMin:
|
||||
'Ši reikšmė turi būti ilgesnė nei minimalus simbolių skaičius, kuris yra {{minLength}} simboliai.',
|
||||
notValidDate: '"{{value}}" nėra galiojanti data.',
|
||||
required: 'Šis laukas yra privalomas.',
|
||||
requiresAtLeast: 'Šis laukas reikalauja bent {{count}} {{label}}.',
|
||||
requiresNoMoreThan: 'Šiame laukelyje gali būti ne daugiau kaip {{count}} {{label}}.',
|
||||
requiresTwoNumbers: 'Šiame lauke reikia įvesti du skaičius.',
|
||||
shorterThanMax: 'Ši reikšmė turi būti trumpesnė nei maksimalus {{maxLength}} simbolių ilgis.',
|
||||
timezoneRequired: 'Reikia nustatyti laiko juostą.',
|
||||
trueOrFalse: 'Šis laukas gali būti lygus tik „true“ ar „false“.',
|
||||
username:
|
||||
'Įveskite galiojantį vartotojo vardą. Galima naudoti raides, skaičius, brūkšnelius, taškus ir pabraukimus.',
|
||||
validUploadID: 'Šis laukas nėra tinkamas įkėlimo ID.',
|
||||
},
|
||||
version: {
|
||||
type: 'Įveskite',
|
||||
aboutToPublishSelection: 'Jūs ketinate išleisti visus {{label}} išrinktame. Ar esate tikri?',
|
||||
aboutToRestore:
|
||||
'Jūs ketinate atkurti šį {{label}} dokumentą į būklę, kurioje jis buvo {{versionDate}}.',
|
||||
aboutToRestoreGlobal:
|
||||
'Jūs ketinate atkurti visuotinę {{label}} būklę, kokia ji buvo {{versionDate}}.',
|
||||
aboutToRevertToPublished:
|
||||
'Jūs ketinate atšaukti šio dokumento pakeitimus ir grįžti prie publikuotos versijos. Ar esate įsitikinęs?',
|
||||
aboutToUnpublish: 'Jūs ketinate panaikinti šio dokumento publikavimą. Ar esate tikri?',
|
||||
aboutToUnpublishSelection:
|
||||
'Jūs ketinate atšaukti visų {{label}} pasirinkime. Ar esate įsitikinęs?',
|
||||
autosave: 'Automatinis išsaugojimas',
|
||||
autosavedSuccessfully: 'Sėkmingai automatiškai išsaugota.',
|
||||
autosavedVersion: 'Automatiškai išsaugota versija',
|
||||
changed: 'Pakeistas',
|
||||
changedFieldsCount_one: '{{count}} pakeistas laukas',
|
||||
changedFieldsCount_other: '{{count}} pakeisti laukai',
|
||||
compareVersion: 'Palyginkite versiją su:',
|
||||
confirmPublish: 'Patvirtinkite publikaciją',
|
||||
confirmRevertToSaved: 'Patvirtinkite grįžimą į įrašytą',
|
||||
confirmUnpublish: 'Patvirtinkite nepublikavimą',
|
||||
confirmVersionRestoration: 'Patvirtinkite versijos atkūrimą',
|
||||
currentDocumentStatus: 'Dabartinis {{docStatus}} dokumentas',
|
||||
currentDraft: 'Dabartinis projektas',
|
||||
currentPublishedVersion: 'Dabartinė publikuota versija',
|
||||
draft: 'Projektas',
|
||||
draftSavedSuccessfully: 'Juosmuo sėkmingai išsaugotas.',
|
||||
lastSavedAgo: 'Paskutinį kartą išsaugota prieš {{distance}}',
|
||||
modifiedOnly: 'Tik modifikuotas',
|
||||
noFurtherVersionsFound: 'Nerasta daugiau versijų',
|
||||
noRowsFound: 'Nerasta {{žymė}}',
|
||||
noRowsSelected: 'Pasirinkta ne viena {{žymė}}',
|
||||
preview: 'Peržiūra',
|
||||
previouslyPublished: 'Ankstesnė publikacija',
|
||||
problemRestoringVersion: 'Buvo problema atkuriant šią versiją',
|
||||
publish: 'Paskelbti',
|
||||
publishAllLocales: 'Publikuokite visus lokalizacijas',
|
||||
publishChanges: 'Paskelbti pakeitimus',
|
||||
published: 'Paskelbta',
|
||||
publishIn: 'Paskelbti {{locale}}',
|
||||
publishing: 'Leidyba',
|
||||
restoreAsDraft: 'Atkurti kaip juodraštį',
|
||||
restoredSuccessfully: 'Sėkmingai atkurtas.',
|
||||
restoreThisVersion: 'Atkurti šią versiją',
|
||||
restoring: 'Atkuriamas...',
|
||||
reverting: 'Grįžtama...',
|
||||
revertToPublished: 'Grįžti prie publikuotojo',
|
||||
saveDraft: 'Išsaugoti juodraštį',
|
||||
scheduledSuccessfully: 'Sėkmingai suplanuota.',
|
||||
schedulePublish: 'Suplanuokite publikaciją',
|
||||
selectLocales: 'Pasirinkite lokales, kurias norėtumėte rodyti',
|
||||
selectVersionToCompare: 'Pasirinkite versiją, kurią norite palyginti',
|
||||
showingVersionsFor: 'Rodomos versijos:',
|
||||
showLocales: 'Rodyti lokalizacijas:',
|
||||
status: 'Būsena',
|
||||
unpublish: 'Nebepublikuoti',
|
||||
unpublishing: 'Nebepublikuojama...',
|
||||
version: 'Versija',
|
||||
versionCount_many: 'Rasta {{count}} versijų',
|
||||
versionCount_none: 'Nerasta jokių versijų',
|
||||
versionCount_one: 'Rasta {{count}} versija',
|
||||
versionCount_other: 'Rasta {{count}} versijų',
|
||||
versionCreatedOn: '{{version}} sukurtas:',
|
||||
versionID: 'Versijos ID',
|
||||
versions: 'Versijos',
|
||||
viewingVersion: 'Peržiūrėkite versiją {{entityLabel}} {{documentTitle}}',
|
||||
viewingVersionGlobal: 'Peržiūrint visuotinę {{entityLabel}} versiją',
|
||||
viewingVersions: 'Peržiūrint versijas {{entityLabel}} {{documentTitle}}',
|
||||
viewingVersionsGlobal: 'Peržiūrėti globalaus {{entityLabel}} versijas',
|
||||
},
|
||||
}
|
||||
|
||||
export const lt: Language = {
|
||||
dateFNSKey: 'lt',
|
||||
translations: ltTranslations,
|
||||
}
|
||||
@@ -23,7 +23,6 @@ type DateFNSKeys =
|
||||
| 'it'
|
||||
| 'ja'
|
||||
| 'ko'
|
||||
| 'lt'
|
||||
| 'nb'
|
||||
| 'nl'
|
||||
| 'pl'
|
||||
|
||||
@@ -21,7 +21,6 @@ export const acceptedLanguages = [
|
||||
'it',
|
||||
'ja',
|
||||
'ko',
|
||||
'lt',
|
||||
'my',
|
||||
'nb',
|
||||
'nl',
|
||||
@@ -86,6 +85,7 @@ export const acceptedLanguages = [
|
||||
* 'ku-Arab',
|
||||
* 'ky-Cyrl',
|
||||
* 'lb',
|
||||
* 'lt',
|
||||
* 'lv',
|
||||
* 'mi-Latn',
|
||||
* 'mk',
|
||||
|
||||
@@ -36,11 +36,8 @@ const useClientPlugin = {
|
||||
|
||||
result.outputFiles.forEach((file) => {
|
||||
let contents = file.text
|
||||
|
||||
if (!file.path.endsWith('.map')) {
|
||||
contents = contents.replace(directiveRegex, '') // Remove existing use client directives
|
||||
contents = directive + '\n' + contents // Prepend our use client directive
|
||||
}
|
||||
contents = contents.replace(directiveRegex, '') // Remove existing use client directives
|
||||
contents = directive + '\n' + contents // Prepend our use client directive
|
||||
|
||||
if (originalWrite) {
|
||||
const filePath = path.join(build.initialOptions.outdir, path.basename(file.path))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@payloadcms/ui",
|
||||
"version": "3.24.0",
|
||||
"version": "3.23.0",
|
||||
"homepage": "https://payloadcms.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -68,20 +68,11 @@
|
||||
background-color: var(--bg-color);
|
||||
box-shadow: var(--box-shadow);
|
||||
border-radius: $style-radius-m;
|
||||
border-left: 1px solid var(--theme-bg);
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
align-items: center;
|
||||
|
||||
html:not([dir='RTL']) & {
|
||||
border-left: 1px solid var(--theme-bg);
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
html[dir='RTL'] & {
|
||||
border-right: 1px solid var(--theme-bg);
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus-visible,
|
||||
&:focus,
|
||||
@@ -245,15 +236,8 @@
|
||||
}
|
||||
|
||||
&--withPopup .btn {
|
||||
html:not([dir='RTL']) & {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
html[dir='RTL'] & {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
&--style-icon-label,
|
||||
|
||||
@@ -185,7 +185,6 @@ export const Button: React.FC<Props> = (props) => {
|
||||
className={disabled && !enableSubMenu ? `${baseClass}--popup-disabled` : ''}
|
||||
disabled={disabled && !enableSubMenu}
|
||||
horizontalAlign="right"
|
||||
id={`${id}-popup`}
|
||||
noBackground
|
||||
render={({ close }) => SubMenuPopupContent({ close: () => close() })}
|
||||
size="large"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import { Modal, useModal } from '@faceless-ui/modal'
|
||||
import React, { useCallback } from 'react'
|
||||
import React from 'react'
|
||||
|
||||
import { useEditDepth } from '../../providers/EditDepth/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
@@ -10,31 +10,34 @@ import './index.scss'
|
||||
|
||||
const baseClass = 'confirmation-modal'
|
||||
|
||||
export type OnConfirm = (args: {
|
||||
closeConfirmationModal: () => void
|
||||
setConfirming: (state: boolean) => void
|
||||
}) => Promise<void> | void
|
||||
|
||||
export type OnCancel = () => void
|
||||
|
||||
export type ConfirmationModalProps = {
|
||||
body: React.ReactNode
|
||||
cancelLabel?: string
|
||||
className?: string
|
||||
confirmingLabel?: string
|
||||
confirmLabel?: string
|
||||
heading: React.ReactNode
|
||||
modalSlug: string
|
||||
onCancel?: OnCancel
|
||||
onConfirm: () => Promise<void> | void
|
||||
onConfirm: OnConfirm
|
||||
}
|
||||
|
||||
export function ConfirmationModal(props: ConfirmationModalProps) {
|
||||
const {
|
||||
body,
|
||||
cancelLabel,
|
||||
className,
|
||||
confirmingLabel,
|
||||
confirmLabel,
|
||||
heading,
|
||||
modalSlug,
|
||||
onCancel: onCancelFromProps,
|
||||
onConfirm: onConfirmFromProps,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
} = props
|
||||
|
||||
const editDepth = useEditDepth()
|
||||
@@ -44,32 +47,9 @@ export function ConfirmationModal(props: ConfirmationModalProps) {
|
||||
const { closeModal } = useModal()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const onConfirm = useCallback(async () => {
|
||||
if (!confirming) {
|
||||
setConfirming(true)
|
||||
|
||||
if (typeof onConfirmFromProps === 'function') {
|
||||
await onConfirmFromProps()
|
||||
}
|
||||
|
||||
setConfirming(false)
|
||||
closeModal(modalSlug)
|
||||
}
|
||||
}, [confirming, onConfirmFromProps, closeModal, modalSlug])
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
if (!confirming) {
|
||||
closeModal(modalSlug)
|
||||
|
||||
if (typeof onCancelFromProps === 'function') {
|
||||
onCancelFromProps()
|
||||
}
|
||||
}
|
||||
}, [confirming, onCancelFromProps, closeModal, modalSlug])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className={[baseClass, className].filter(Boolean).join(' ')}
|
||||
className={baseClass}
|
||||
slug={modalSlug}
|
||||
style={{
|
||||
zIndex: drawerZBase + editDepth,
|
||||
@@ -85,15 +65,36 @@ export function ConfirmationModal(props: ConfirmationModalProps) {
|
||||
buttonStyle="secondary"
|
||||
disabled={confirming}
|
||||
id="confirm-cancel"
|
||||
onClick={onCancel}
|
||||
onClick={
|
||||
confirming
|
||||
? undefined
|
||||
: () => {
|
||||
closeModal(modalSlug)
|
||||
if (typeof onCancel === 'function') {
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
size="large"
|
||||
type="button"
|
||||
>
|
||||
{cancelLabel || t('general:cancel')}
|
||||
</Button>
|
||||
<Button id="confirm-action" onClick={onConfirm} size="large">
|
||||
<Button
|
||||
id="confirm-action"
|
||||
onClick={() => {
|
||||
if (!confirming) {
|
||||
setConfirming(true)
|
||||
void onConfirm({
|
||||
closeConfirmationModal: () => closeModal(modalSlug),
|
||||
setConfirming: (state) => setConfirming(state),
|
||||
})
|
||||
}
|
||||
}}
|
||||
size="large"
|
||||
>
|
||||
{confirming
|
||||
? confirmingLabel || `${t('general:loading')}...`
|
||||
? confirmingLabel || t('general:loading')
|
||||
: confirmLabel || t('general:confirm')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,6 @@ const baseClass = 'date-time-picker'
|
||||
|
||||
const DatePicker: React.FC<Props> = (props) => {
|
||||
const {
|
||||
id,
|
||||
displayFormat: customDisplayFormat,
|
||||
maxDate,
|
||||
maxTime,
|
||||
@@ -114,7 +113,7 @@ const DatePicker: React.FC<Props> = (props) => {
|
||||
}, [i18n.language, i18n.dateFNS])
|
||||
|
||||
return (
|
||||
<div className={classes} id={id}>
|
||||
<div className={classes}>
|
||||
<div className={`${baseClass}__icon-wrap`}>
|
||||
{dateTimePickerProps.selected && (
|
||||
<button
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { DayPickerProps, SharedProps, TimePickerProps } from 'payload'
|
||||
|
||||
export type Props = {
|
||||
id?: string
|
||||
onChange?: (val: Date) => void
|
||||
placeholder?: string
|
||||
readOnly?: boolean
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useRouter } from 'next/navigation.js'
|
||||
import React, { useCallback } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import type { OnConfirm } from '../ConfirmationModal/index.js'
|
||||
import type { DocumentDrawerContextType } from '../DocumentDrawer/Provider.js'
|
||||
|
||||
import { useForm } from '../../forms/Form/context.js'
|
||||
@@ -68,79 +69,86 @@ export const DeleteDocument: React.FC<Props> = (props) => {
|
||||
toast.error(t('error:deletingTitle', { title }))
|
||||
}, [t, title])
|
||||
|
||||
const handleDelete = useCallback(async () => {
|
||||
setModified(false)
|
||||
const handleDelete: OnConfirm = useCallback(
|
||||
async ({ closeConfirmationModal, setConfirming }) => {
|
||||
setModified(false)
|
||||
|
||||
try {
|
||||
await requests
|
||||
.delete(`${serverURL}${api}/${collectionSlug}/${id}`, {
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(async (res) => {
|
||||
try {
|
||||
const json = await res.json()
|
||||
try {
|
||||
await requests
|
||||
.delete(`${serverURL}${api}/${collectionSlug}/${id}`, {
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(async (res) => {
|
||||
try {
|
||||
const json = await res.json()
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
|
||||
if (res.status < 400) {
|
||||
toast.success(
|
||||
t('general:titleDeleted', {
|
||||
label: getTranslation(singularLabel, i18n),
|
||||
title,
|
||||
}) || json.message,
|
||||
)
|
||||
|
||||
if (redirectAfterDelete) {
|
||||
return startRouteTransition(() =>
|
||||
router.push(
|
||||
formatAdminURL({
|
||||
adminRoute,
|
||||
path: `/collections/${collectionSlug}`,
|
||||
}),
|
||||
),
|
||||
if (res.status < 400) {
|
||||
toast.success(
|
||||
t('general:titleDeleted', {
|
||||
label: getTranslation(singularLabel, i18n),
|
||||
title,
|
||||
}) || json.message,
|
||||
)
|
||||
|
||||
if (redirectAfterDelete) {
|
||||
return startRouteTransition(() =>
|
||||
router.push(
|
||||
formatAdminURL({
|
||||
adminRoute,
|
||||
path: `/collections/${collectionSlug}`,
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if (typeof onDelete === 'function') {
|
||||
await onDelete({ id, collectionConfig })
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof onDelete === 'function') {
|
||||
await onDelete({ id, collectionConfig })
|
||||
if (json.errors) {
|
||||
json.errors.forEach((error) => toast.error(error.message))
|
||||
} else {
|
||||
addDefaultError()
|
||||
}
|
||||
|
||||
return
|
||||
return false
|
||||
} catch (_err) {
|
||||
return addDefaultError()
|
||||
}
|
||||
|
||||
if (json.errors) {
|
||||
json.errors.forEach((error) => toast.error(error.message))
|
||||
} else {
|
||||
addDefaultError()
|
||||
}
|
||||
|
||||
return false
|
||||
} catch (_err) {
|
||||
return addDefaultError()
|
||||
}
|
||||
})
|
||||
} catch (_err) {
|
||||
return addDefaultError()
|
||||
}
|
||||
}, [
|
||||
setModified,
|
||||
serverURL,
|
||||
api,
|
||||
collectionSlug,
|
||||
id,
|
||||
t,
|
||||
singularLabel,
|
||||
addDefaultError,
|
||||
i18n,
|
||||
title,
|
||||
router,
|
||||
adminRoute,
|
||||
redirectAfterDelete,
|
||||
onDelete,
|
||||
collectionConfig,
|
||||
startRouteTransition,
|
||||
])
|
||||
})
|
||||
} catch (_err) {
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
return addDefaultError()
|
||||
}
|
||||
},
|
||||
[
|
||||
setModified,
|
||||
serverURL,
|
||||
api,
|
||||
collectionSlug,
|
||||
id,
|
||||
t,
|
||||
singularLabel,
|
||||
addDefaultError,
|
||||
i18n,
|
||||
title,
|
||||
router,
|
||||
adminRoute,
|
||||
redirectAfterDelete,
|
||||
onDelete,
|
||||
collectionConfig,
|
||||
startRouteTransition,
|
||||
],
|
||||
)
|
||||
|
||||
if (id) {
|
||||
return (
|
||||
|
||||
@@ -8,6 +8,8 @@ import * as qs from 'qs-esm'
|
||||
import React, { useCallback } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import type { OnConfirm } from '../ConfirmationModal/index.js'
|
||||
|
||||
import { useAuth } from '../../providers/Auth/index.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
||||
@@ -52,87 +54,94 @@ export const DeleteMany: React.FC<Props> = (props) => {
|
||||
toast.error(t('error:unknown'))
|
||||
}, [t])
|
||||
|
||||
const handleDelete = useCallback(async () => {
|
||||
const queryWithSearch = mergeListSearchAndWhere({
|
||||
collectionConfig: collection,
|
||||
search: searchParams.get('search'),
|
||||
})
|
||||
|
||||
const queryString = getQueryParams(queryWithSearch)
|
||||
|
||||
await requests
|
||||
.delete(`${serverURL}${api}/${slug}${queryString}`, {
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
const handleDelete: OnConfirm = useCallback(
|
||||
async ({ closeConfirmationModal, setConfirming }) => {
|
||||
const queryWithSearch = mergeListSearchAndWhere({
|
||||
collectionConfig: collection,
|
||||
search: searchParams.get('search'),
|
||||
})
|
||||
.then(async (res) => {
|
||||
try {
|
||||
const json = await res.json()
|
||||
|
||||
const deletedDocs = json?.docs.length || 0
|
||||
const successLabel = deletedDocs > 1 ? plural : singular
|
||||
const queryString = getQueryParams(queryWithSearch)
|
||||
|
||||
if (res.status < 400 || deletedDocs > 0) {
|
||||
toast.success(
|
||||
t('general:deletedCountSuccessfully', {
|
||||
count: deletedDocs,
|
||||
label: getTranslation(successLabel, i18n),
|
||||
}),
|
||||
)
|
||||
await requests
|
||||
.delete(`${serverURL}${api}/${slug}${queryString}`, {
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(async (res) => {
|
||||
try {
|
||||
const json = await res.json()
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
|
||||
if (json?.errors.length > 0) {
|
||||
const deletedDocs = json?.docs.length || 0
|
||||
const successLabel = deletedDocs > 1 ? plural : singular
|
||||
|
||||
if (res.status < 400 || deletedDocs > 0) {
|
||||
toast.success(
|
||||
t('general:deletedCountSuccessfully', {
|
||||
count: deletedDocs,
|
||||
label: getTranslation(successLabel, i18n),
|
||||
}),
|
||||
)
|
||||
|
||||
if (json?.errors.length > 0) {
|
||||
toast.error(json.message, {
|
||||
description: json.errors.map((error) => error.message).join('\n'),
|
||||
})
|
||||
}
|
||||
|
||||
toggleAll()
|
||||
|
||||
router.replace(
|
||||
qs.stringify(
|
||||
{
|
||||
page: selectAll ? '1' : undefined,
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
),
|
||||
)
|
||||
|
||||
clearRouteCache()
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
if (json.errors) {
|
||||
toast.error(json.message, {
|
||||
description: json.errors.map((error) => error.message).join('\n'),
|
||||
})
|
||||
} else {
|
||||
return addDefaultError()
|
||||
}
|
||||
|
||||
toggleAll()
|
||||
|
||||
router.replace(
|
||||
qs.stringify(
|
||||
{
|
||||
page: selectAll ? '1' : undefined,
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
),
|
||||
)
|
||||
|
||||
clearRouteCache()
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
if (json.errors) {
|
||||
toast.error(json.message, {
|
||||
description: json.errors.map((error) => error.message).join('\n'),
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
} catch (_err) {
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
return addDefaultError()
|
||||
}
|
||||
return false
|
||||
} catch (_err) {
|
||||
return addDefaultError()
|
||||
}
|
||||
})
|
||||
}, [
|
||||
searchParams,
|
||||
addDefaultError,
|
||||
api,
|
||||
getQueryParams,
|
||||
i18n,
|
||||
plural,
|
||||
router,
|
||||
selectAll,
|
||||
serverURL,
|
||||
singular,
|
||||
slug,
|
||||
t,
|
||||
toggleAll,
|
||||
clearRouteCache,
|
||||
collection,
|
||||
])
|
||||
})
|
||||
},
|
||||
[
|
||||
searchParams,
|
||||
addDefaultError,
|
||||
api,
|
||||
getQueryParams,
|
||||
i18n,
|
||||
plural,
|
||||
router,
|
||||
selectAll,
|
||||
serverURL,
|
||||
singular,
|
||||
slug,
|
||||
t,
|
||||
toggleAll,
|
||||
clearRouteCache,
|
||||
collection,
|
||||
],
|
||||
)
|
||||
|
||||
if (selectAll === SelectAllStatus.None || !hasDeletePermission) {
|
||||
return null
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useRouter } from 'next/navigation.js'
|
||||
import React, { useCallback } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import type { OnConfirm } from '../ConfirmationModal/index.js'
|
||||
import type { DocumentDrawerContextType } from '../DocumentDrawer/Provider.js'
|
||||
|
||||
import { useForm, useFormModified } from '../../forms/Form/context.js'
|
||||
@@ -57,77 +58,91 @@ export const DuplicateDocument: React.FC<Props> = ({
|
||||
|
||||
const modalSlug = `duplicate-${id}`
|
||||
|
||||
const duplicate = useCallback(async () => {
|
||||
setRenderModal(true)
|
||||
const duplicate = useCallback(
|
||||
async ({ onResponse }: { onResponse?: () => void } = {}) => {
|
||||
setRenderModal(true)
|
||||
|
||||
await requests
|
||||
.post(
|
||||
`${serverURL}${apiRoute}/${slug}/${id}/duplicate${locale?.code ? `?locale=${locale.code}` : ''}`,
|
||||
{
|
||||
body: JSON.stringify({}),
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
'Content-Type': 'application/json',
|
||||
credentials: 'include',
|
||||
await requests
|
||||
.post(
|
||||
`${serverURL}${apiRoute}/${slug}/${id}/duplicate${locale?.code ? `?locale=${locale.code}` : ''}`,
|
||||
{
|
||||
body: JSON.stringify({}),
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
'Content-Type': 'application/json',
|
||||
credentials: 'include',
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
.then(async (res) => {
|
||||
const { doc, errors, message } = await res.json()
|
||||
)
|
||||
.then(async (res) => {
|
||||
const { doc, errors, message } = await res.json()
|
||||
if (typeof onResponse === 'function') {
|
||||
onResponse()
|
||||
}
|
||||
|
||||
if (res.status < 400) {
|
||||
toast.success(
|
||||
message ||
|
||||
t('general:successfullyDuplicated', { label: getTranslation(singularLabel, i18n) }),
|
||||
)
|
||||
if (res.status < 400) {
|
||||
toast.success(
|
||||
message ||
|
||||
t('general:successfullyDuplicated', { label: getTranslation(singularLabel, i18n) }),
|
||||
)
|
||||
|
||||
setModified(false)
|
||||
setModified(false)
|
||||
|
||||
if (redirectAfterDuplicate) {
|
||||
return startRouteTransition(() =>
|
||||
router.push(
|
||||
formatAdminURL({
|
||||
adminRoute,
|
||||
path: `/collections/${slug}/${doc.id}${locale?.code ? `?locale=${locale.code}` : ''}`,
|
||||
}),
|
||||
),
|
||||
if (redirectAfterDuplicate) {
|
||||
return startRouteTransition(() =>
|
||||
router.push(
|
||||
formatAdminURL({
|
||||
adminRoute,
|
||||
path: `/collections/${slug}/${doc.id}${locale?.code ? `?locale=${locale.code}` : ''}`,
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if (typeof onDuplicate === 'function') {
|
||||
void onDuplicate({ collectionConfig, doc })
|
||||
}
|
||||
} else {
|
||||
toast.error(
|
||||
errors?.[0].message ||
|
||||
message ||
|
||||
t('error:unspecific', { label: getTranslation(singularLabel, i18n) }),
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
[
|
||||
locale,
|
||||
serverURL,
|
||||
apiRoute,
|
||||
slug,
|
||||
id,
|
||||
i18n,
|
||||
t,
|
||||
singularLabel,
|
||||
onDuplicate,
|
||||
redirectAfterDuplicate,
|
||||
setModified,
|
||||
router,
|
||||
adminRoute,
|
||||
collectionConfig,
|
||||
startRouteTransition,
|
||||
],
|
||||
)
|
||||
|
||||
if (typeof onDuplicate === 'function') {
|
||||
void onDuplicate({ collectionConfig, doc })
|
||||
}
|
||||
} else {
|
||||
toast.error(
|
||||
errors?.[0].message ||
|
||||
message ||
|
||||
t('error:unspecific', { label: getTranslation(singularLabel, i18n) }),
|
||||
)
|
||||
}
|
||||
const onConfirm: OnConfirm = useCallback(
|
||||
async ({ closeConfirmationModal, setConfirming }) => {
|
||||
setRenderModal(false)
|
||||
|
||||
await duplicate({
|
||||
onResponse: () => {
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
},
|
||||
})
|
||||
}, [
|
||||
locale,
|
||||
serverURL,
|
||||
apiRoute,
|
||||
slug,
|
||||
id,
|
||||
i18n,
|
||||
t,
|
||||
singularLabel,
|
||||
onDuplicate,
|
||||
redirectAfterDuplicate,
|
||||
setModified,
|
||||
router,
|
||||
adminRoute,
|
||||
collectionConfig,
|
||||
startRouteTransition,
|
||||
])
|
||||
|
||||
const onConfirm = useCallback(async () => {
|
||||
setRenderModal(false)
|
||||
|
||||
await duplicate()
|
||||
}, [duplicate])
|
||||
},
|
||||
[duplicate],
|
||||
)
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
||||
@@ -3,6 +3,8 @@ import { useModal } from '@faceless-ui/modal'
|
||||
import React, { useCallback } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import type { OnConfirm } from '../ConfirmationModal/index.js'
|
||||
|
||||
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
||||
import { useTranslation } from '../../providers/Translation/index.js'
|
||||
import { Button } from '../Button/index.js'
|
||||
@@ -23,11 +25,16 @@ export function GenerateConfirmation(props: GenerateConfirmationProps) {
|
||||
|
||||
const modalSlug = `generate-confirmation-${id}`
|
||||
|
||||
const handleGenerate = useCallback(() => {
|
||||
setKey()
|
||||
toast.success(t('authentication:newAPIKeyGenerated'))
|
||||
highlightField(true)
|
||||
}, [highlightField, setKey, t])
|
||||
const handleGenerate: OnConfirm = useCallback(
|
||||
({ closeConfirmationModal, setConfirming }) => {
|
||||
setKey()
|
||||
toast.success(t('authentication:newAPIKeyGenerated'))
|
||||
highlightField(true)
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
},
|
||||
[highlightField, setKey, t],
|
||||
)
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
import React, { useCallback } from 'react'
|
||||
|
||||
import type { OnCancel } from '../ConfirmationModal/index.js'
|
||||
import type { OnCancel, OnConfirm } from '../ConfirmationModal/index.js'
|
||||
|
||||
import { useForm, useFormModified } from '../../forms/Form/index.js'
|
||||
import { useAuth } from '../../providers/Auth/index.js'
|
||||
@@ -36,8 +36,10 @@ export const LeaveWithoutSaving: React.FC = () => {
|
||||
closeModal(modalSlug)
|
||||
}, [closeModal])
|
||||
|
||||
const onConfirm = useCallback(() => {
|
||||
const onConfirm: OnConfirm = useCallback(({ closeConfirmationModal, setConfirming }) => {
|
||||
setHasAccepted(true)
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
|
||||
@@ -21,7 +21,6 @@ export const LocalizerLabel: React.FC<{
|
||||
<div
|
||||
aria-label={ariaLabel || t('general:locale')}
|
||||
className={[baseClass, className].filter(Boolean).join(' ')}
|
||||
data-locale={locale ? locale.code : undefined}
|
||||
>
|
||||
<div className={`${baseClass}__label`}>{`${t('general:locale')}:`} </div>
|
||||
<div className={`${baseClass}__current`}>
|
||||
|
||||
@@ -61,17 +61,12 @@ export const Localizer: React.FC<{
|
||||
<Fragment>
|
||||
{localeOptionLabel}
|
||||
|
||||
<span
|
||||
className={`${baseClass}__locale-code`}
|
||||
data-locale={localeOption.code}
|
||||
>
|
||||
<span className={`${baseClass}__locale-code`}>
|
||||
{`(${localeOption.code})`}
|
||||
</span>
|
||||
</Fragment>
|
||||
) : (
|
||||
<span className={`${baseClass}__locale-code`} data-locale={localeOption.code}>
|
||||
{localeOptionLabel}
|
||||
</span>
|
||||
<span className={`${baseClass}__locale-code`}>{localeOptionLabel}</span>
|
||||
)}
|
||||
</PopupList.Button>
|
||||
)
|
||||
|
||||
@@ -25,7 +25,6 @@ export type PopupProps = {
|
||||
disabled?: boolean
|
||||
forceOpen?: boolean
|
||||
horizontalAlign?: 'center' | 'left' | 'right'
|
||||
id?: string
|
||||
initActive?: boolean
|
||||
noBackground?: boolean
|
||||
onToggleOpen?: (active: boolean) => void
|
||||
@@ -38,7 +37,6 @@ export type PopupProps = {
|
||||
|
||||
export const Popup: React.FC<PopupProps> = (props) => {
|
||||
const {
|
||||
id,
|
||||
boundingRef,
|
||||
button,
|
||||
buttonClassName,
|
||||
@@ -170,7 +168,7 @@ export const Popup: React.FC<PopupProps> = (props) => {
|
||||
.join(' ')
|
||||
|
||||
return (
|
||||
<div className={classes} id={id}>
|
||||
<div className={classes}>
|
||||
<div className={`${baseClass}__trigger-wrap`}>
|
||||
{showOnHover ? (
|
||||
<div
|
||||
|
||||
@@ -36,7 +36,6 @@ import { buildUpcomingColumns } from './buildUpcomingColumns.js'
|
||||
const baseClass = 'schedule-publish'
|
||||
|
||||
type Props = {
|
||||
defaultType?: PublishType
|
||||
slug: string
|
||||
}
|
||||
|
||||
@@ -45,7 +44,7 @@ const defaultLocaleOption = {
|
||||
value: 'all',
|
||||
}
|
||||
|
||||
export const ScheduleDrawer: React.FC<Props> = ({ slug, defaultType }) => {
|
||||
export const ScheduleDrawer: React.FC<Props> = ({ slug }) => {
|
||||
const { toggleModal } = useModal()
|
||||
const {
|
||||
config: {
|
||||
@@ -61,7 +60,7 @@ export const ScheduleDrawer: React.FC<Props> = ({ slug, defaultType }) => {
|
||||
const { id, collectionSlug, globalSlug, title } = useDocumentInfo()
|
||||
const { i18n, t } = useTranslation()
|
||||
const { schedulePublish } = useServerFunctions()
|
||||
const [type, setType] = React.useState<PublishType>(defaultType || 'publish')
|
||||
const [type, setType] = React.useState<PublishType>('publish')
|
||||
const [date, setDate] = React.useState<Date>()
|
||||
const [timezone, setTimezone] = React.useState<string>(defaultTimezone)
|
||||
const [locale, setLocale] = React.useState<{ label: string; value: string }>(defaultLocaleOption)
|
||||
@@ -314,9 +313,8 @@ export const ScheduleDrawer: React.FC<Props> = ({ slug, defaultType }) => {
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
<FieldLabel label={t('general:time')} path={'time'} required />
|
||||
<FieldLabel label={t('general:time')} required />
|
||||
<DatePickerField
|
||||
id="time"
|
||||
minDate={new Date()}
|
||||
onChange={(e) => onChangeDate(e)}
|
||||
pickerAppearance="dayAndTime"
|
||||
@@ -345,13 +343,7 @@ export const ScheduleDrawer: React.FC<Props> = ({ slug, defaultType }) => {
|
||||
</React.Fragment>
|
||||
)}
|
||||
<div className={`${baseClass}__actions`}>
|
||||
<Button
|
||||
buttonStyle="primary"
|
||||
disabled={processing}
|
||||
id="scheduled-publish-save"
|
||||
onClick={handleSave}
|
||||
type="button"
|
||||
>
|
||||
<Button buttonStyle="primary" disabled={processing} onClick={handleSave} type="button">
|
||||
{t('general:save')}
|
||||
</Button>
|
||||
{processing ? <span>{t('general:saving')}</span> : null}
|
||||
|
||||
@@ -73,10 +73,7 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
|
||||
entityConfig?.versions?.drafts.schedulePublish
|
||||
|
||||
const canSchedulePublish = Boolean(
|
||||
scheduledPublishEnabled &&
|
||||
hasPublishPermission &&
|
||||
(globalSlug || (collectionSlug && id)) &&
|
||||
!modified,
|
||||
scheduledPublishEnabled && hasPublishPermission && (globalSlug || (collectionSlug && id)),
|
||||
)
|
||||
|
||||
const operation = useOperation()
|
||||
@@ -212,10 +209,7 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
|
||||
<React.Fragment>
|
||||
{canSchedulePublish && (
|
||||
<PopupList.ButtonGroup key="schedule-publish">
|
||||
<PopupList.Button
|
||||
id="schedule-publish"
|
||||
onClick={() => [toggleModal(drawerSlug), close()]}
|
||||
>
|
||||
<PopupList.Button onClick={() => [toggleModal(drawerSlug), close()]}>
|
||||
{t('version:schedulePublish')}
|
||||
</PopupList.Button>
|
||||
</PopupList.ButtonGroup>
|
||||
@@ -236,12 +230,7 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
|
||||
>
|
||||
{localization ? defaultLabel : label}
|
||||
</FormSubmit>
|
||||
{canSchedulePublish && isModalOpen(drawerSlug) && (
|
||||
<ScheduleDrawer
|
||||
defaultType={!hasNewerVersions ? 'unpublish' : 'publish'}
|
||||
slug={drawerSlug}
|
||||
/>
|
||||
)}
|
||||
{canSchedulePublish && isModalOpen(drawerSlug) && <ScheduleDrawer slug={drawerSlug} />}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import * as qs from 'qs-esm'
|
||||
import React, { useCallback } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import type { OnConfirm } from '../ConfirmationModal/index.js'
|
||||
|
||||
import { useAuth } from '../../providers/Auth/index.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
||||
@@ -52,80 +54,87 @@ export const PublishMany: React.FC<PublishManyProps> = (props) => {
|
||||
toast.error(t('error:unknown'))
|
||||
}, [t])
|
||||
|
||||
const handlePublish = useCallback(async () => {
|
||||
await requests
|
||||
.patch(
|
||||
`${serverURL}${api}/${slug}${getQueryParams({ _status: { not_equals: 'published' } })}&draft=true`,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
_status: 'published',
|
||||
}),
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
'Content-Type': 'application/json',
|
||||
const handlePublish: OnConfirm = useCallback(
|
||||
async ({ closeConfirmationModal, setConfirming }) => {
|
||||
await requests
|
||||
.patch(
|
||||
`${serverURL}${api}/${slug}${getQueryParams({ _status: { not_equals: 'published' } })}&draft=true`,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
_status: 'published',
|
||||
}),
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
.then(async (res) => {
|
||||
try {
|
||||
const json = await res.json()
|
||||
)
|
||||
.then(async (res) => {
|
||||
try {
|
||||
const json = await res.json()
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
|
||||
const deletedDocs = json?.docs.length || 0
|
||||
const successLabel = deletedDocs > 1 ? plural : singular
|
||||
const deletedDocs = json?.docs.length || 0
|
||||
const successLabel = deletedDocs > 1 ? plural : singular
|
||||
|
||||
if (res.status < 400 || deletedDocs > 0) {
|
||||
toast.success(
|
||||
t('general:updatedCountSuccessfully', {
|
||||
count: deletedDocs,
|
||||
label: getTranslation(successLabel, i18n),
|
||||
}),
|
||||
)
|
||||
if (res.status < 400 || deletedDocs > 0) {
|
||||
toast.success(
|
||||
t('general:updatedCountSuccessfully', {
|
||||
count: deletedDocs,
|
||||
label: getTranslation(successLabel, i18n),
|
||||
}),
|
||||
)
|
||||
|
||||
if (json?.errors.length > 0) {
|
||||
toast.error(json.message, {
|
||||
description: json.errors.map((error) => error.message).join('\n'),
|
||||
})
|
||||
if (json?.errors.length > 0) {
|
||||
toast.error(json.message, {
|
||||
description: json.errors.map((error) => error.message).join('\n'),
|
||||
})
|
||||
}
|
||||
|
||||
router.replace(
|
||||
qs.stringify(
|
||||
{
|
||||
...parseSearchParams(searchParams),
|
||||
page: selectAll ? '1' : undefined,
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
),
|
||||
)
|
||||
|
||||
clearRouteCache() // Use clearRouteCache instead of router.refresh, as we only need to clear the cache if the user has route caching enabled - clearRouteCache checks for this
|
||||
return null
|
||||
}
|
||||
|
||||
router.replace(
|
||||
qs.stringify(
|
||||
{
|
||||
...parseSearchParams(searchParams),
|
||||
page: selectAll ? '1' : undefined,
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
),
|
||||
)
|
||||
|
||||
clearRouteCache() // Use clearRouteCache instead of router.refresh, as we only need to clear the cache if the user has route caching enabled - clearRouteCache checks for this
|
||||
return null
|
||||
if (json.errors) {
|
||||
json.errors.forEach((error) => toast.error(error.message))
|
||||
} else {
|
||||
addDefaultError()
|
||||
}
|
||||
return false
|
||||
} catch (_err) {
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
return addDefaultError()
|
||||
}
|
||||
|
||||
if (json.errors) {
|
||||
json.errors.forEach((error) => toast.error(error.message))
|
||||
} else {
|
||||
addDefaultError()
|
||||
}
|
||||
return false
|
||||
} catch (_err) {
|
||||
return addDefaultError()
|
||||
}
|
||||
})
|
||||
}, [
|
||||
serverURL,
|
||||
api,
|
||||
slug,
|
||||
getQueryParams,
|
||||
i18n,
|
||||
plural,
|
||||
singular,
|
||||
t,
|
||||
router,
|
||||
searchParams,
|
||||
selectAll,
|
||||
clearRouteCache,
|
||||
addDefaultError,
|
||||
])
|
||||
})
|
||||
},
|
||||
[
|
||||
serverURL,
|
||||
api,
|
||||
slug,
|
||||
getQueryParams,
|
||||
i18n,
|
||||
plural,
|
||||
singular,
|
||||
t,
|
||||
router,
|
||||
searchParams,
|
||||
selectAll,
|
||||
clearRouteCache,
|
||||
addDefaultError,
|
||||
],
|
||||
)
|
||||
|
||||
if (!versions?.drafts || selectAll === SelectAllStatus.None || !hasPermission) {
|
||||
return null
|
||||
|
||||
@@ -3,6 +3,8 @@ import { useModal } from '@faceless-ui/modal'
|
||||
import React, { useCallback } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import type { OnConfirm } from '../ConfirmationModal/index.js'
|
||||
|
||||
import { useForm } from '../../forms/Form/context.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
||||
@@ -53,7 +55,10 @@ export const Status: React.FC = () => {
|
||||
}
|
||||
|
||||
const performAction = useCallback(
|
||||
async (action: 'revert' | 'unpublish') => {
|
||||
async (
|
||||
action: 'revert' | 'unpublish',
|
||||
{ closeConfirmationModal, setConfirming }: Parameters<OnConfirm>[0],
|
||||
) => {
|
||||
let url
|
||||
let method
|
||||
let body
|
||||
@@ -95,6 +100,9 @@ export const Status: React.FC = () => {
|
||||
},
|
||||
})
|
||||
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
|
||||
if (res.status === 200) {
|
||||
let data
|
||||
const json = await res.json()
|
||||
@@ -163,7 +171,7 @@ export const Status: React.FC = () => {
|
||||
confirmingLabel={t('version:unpublishing')}
|
||||
heading={t('version:confirmUnpublish')}
|
||||
modalSlug={unPublishModalSlug}
|
||||
onConfirm={() => performAction('unpublish')}
|
||||
onConfirm={(args) => performAction('unpublish', args)}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
@@ -183,7 +191,7 @@ export const Status: React.FC = () => {
|
||||
confirmingLabel={t('version:reverting')}
|
||||
heading={t('version:confirmRevertToSaved')}
|
||||
modalSlug={revertModalSlug}
|
||||
onConfirm={() => performAction('revert')}
|
||||
onConfirm={(args) => performAction('revert', args)}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { useRouter } from 'next/navigation.js'
|
||||
import React, { useCallback } from 'react'
|
||||
|
||||
import type { OnCancel } from '../ConfirmationModal/index.js'
|
||||
import type { OnCancel, OnConfirm } from '../ConfirmationModal/index.js'
|
||||
|
||||
import { useAuth } from '../../providers/Auth/index.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
@@ -29,16 +29,22 @@ export const StayLoggedInModal: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { startRouteTransition } = useRouteTransition()
|
||||
|
||||
const onConfirm = useCallback(() => {
|
||||
return startRouteTransition(() =>
|
||||
router.push(
|
||||
formatAdminURL({
|
||||
adminRoute,
|
||||
path: logoutRoute,
|
||||
}),
|
||||
),
|
||||
)
|
||||
}, [router, startRouteTransition, adminRoute, logoutRoute])
|
||||
const onConfirm: OnConfirm = useCallback(
|
||||
({ closeConfirmationModal, setConfirming }) => {
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
|
||||
startRouteTransition(() =>
|
||||
router.push(
|
||||
formatAdminURL({
|
||||
adminRoute,
|
||||
path: logoutRoute,
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
[router, startRouteTransition, adminRoute, logoutRoute],
|
||||
)
|
||||
|
||||
const onCancel: OnCancel = useCallback(() => {
|
||||
refreshCookie()
|
||||
|
||||
@@ -8,6 +8,8 @@ import * as qs from 'qs-esm'
|
||||
import React, { useCallback } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import type { OnConfirm } from '../ConfirmationModal/index.js'
|
||||
|
||||
import { useAuth } from '../../providers/Auth/index.js'
|
||||
import { useConfig } from '../../providers/Config/index.js'
|
||||
import { useRouteCache } from '../../providers/RouteCache/index.js'
|
||||
@@ -51,78 +53,87 @@ export const UnpublishMany: React.FC<UnpublishManyProps> = (props) => {
|
||||
toast.error(t('error:unknown'))
|
||||
}, [t])
|
||||
|
||||
const handleUnpublish = useCallback(async () => {
|
||||
await requests
|
||||
.patch(`${serverURL}${api}/${slug}${getQueryParams({ _status: { not_equals: 'draft' } })}`, {
|
||||
body: JSON.stringify({
|
||||
_status: 'draft',
|
||||
}),
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(async (res) => {
|
||||
try {
|
||||
const json = await res.json()
|
||||
const handleUnpublish: OnConfirm = useCallback(
|
||||
async ({ closeConfirmationModal, setConfirming }) => {
|
||||
await requests
|
||||
.patch(
|
||||
`${serverURL}${api}/${slug}${getQueryParams({ _status: { not_equals: 'draft' } })}`,
|
||||
{
|
||||
body: JSON.stringify({
|
||||
_status: 'draft',
|
||||
}),
|
||||
headers: {
|
||||
'Accept-Language': i18n.language,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
)
|
||||
.then(async (res) => {
|
||||
try {
|
||||
const json = await res.json()
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
|
||||
const deletedDocs = json?.docs.length || 0
|
||||
const successLabel = deletedDocs > 1 ? plural : singular
|
||||
const deletedDocs = json?.docs.length || 0
|
||||
const successLabel = deletedDocs > 1 ? plural : singular
|
||||
|
||||
if (res.status < 400 || deletedDocs > 0) {
|
||||
toast.success(
|
||||
t('general:updatedCountSuccessfully', {
|
||||
count: deletedDocs,
|
||||
label: getTranslation(successLabel, i18n),
|
||||
}),
|
||||
)
|
||||
if (res.status < 400 || deletedDocs > 0) {
|
||||
toast.success(
|
||||
t('general:updatedCountSuccessfully', {
|
||||
count: deletedDocs,
|
||||
label: getTranslation(successLabel, i18n),
|
||||
}),
|
||||
)
|
||||
|
||||
if (json?.errors.length > 0) {
|
||||
toast.error(json.message, {
|
||||
description: json.errors.map((error) => error.message).join('\n'),
|
||||
})
|
||||
if (json?.errors.length > 0) {
|
||||
toast.error(json.message, {
|
||||
description: json.errors.map((error) => error.message).join('\n'),
|
||||
})
|
||||
}
|
||||
|
||||
router.replace(
|
||||
qs.stringify(
|
||||
{
|
||||
...parseSearchParams(searchParams),
|
||||
page: selectAll ? '1' : undefined,
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
),
|
||||
)
|
||||
|
||||
clearRouteCache() // Use clearRouteCache instead of router.refresh, as we only need to clear the cache if the user has route caching enabled - clearRouteCache checks for this
|
||||
return null
|
||||
}
|
||||
|
||||
router.replace(
|
||||
qs.stringify(
|
||||
{
|
||||
...parseSearchParams(searchParams),
|
||||
page: selectAll ? '1' : undefined,
|
||||
},
|
||||
{ addQueryPrefix: true },
|
||||
),
|
||||
)
|
||||
|
||||
clearRouteCache() // Use clearRouteCache instead of router.refresh, as we only need to clear the cache if the user has route caching enabled - clearRouteCache checks for this
|
||||
|
||||
return null
|
||||
if (json.errors) {
|
||||
json.errors.forEach((error) => toast.error(error.message))
|
||||
} else {
|
||||
addDefaultError()
|
||||
}
|
||||
return false
|
||||
} catch (_err) {
|
||||
setConfirming(false)
|
||||
closeConfirmationModal()
|
||||
return addDefaultError()
|
||||
}
|
||||
|
||||
if (json.errors) {
|
||||
json.errors.forEach((error) => toast.error(error.message))
|
||||
} else {
|
||||
addDefaultError()
|
||||
}
|
||||
return false
|
||||
} catch (_err) {
|
||||
return addDefaultError()
|
||||
}
|
||||
})
|
||||
}, [
|
||||
serverURL,
|
||||
api,
|
||||
slug,
|
||||
getQueryParams,
|
||||
i18n,
|
||||
plural,
|
||||
singular,
|
||||
t,
|
||||
router,
|
||||
searchParams,
|
||||
selectAll,
|
||||
clearRouteCache,
|
||||
addDefaultError,
|
||||
])
|
||||
})
|
||||
},
|
||||
[
|
||||
serverURL,
|
||||
api,
|
||||
slug,
|
||||
getQueryParams,
|
||||
i18n,
|
||||
plural,
|
||||
singular,
|
||||
t,
|
||||
router,
|
||||
searchParams,
|
||||
selectAll,
|
||||
clearRouteCache,
|
||||
addDefaultError,
|
||||
],
|
||||
)
|
||||
|
||||
if (!versions?.drafts || selectAll === SelectAllStatus.None || !hasPermission) {
|
||||
return null
|
||||
|
||||
@@ -24,7 +24,7 @@ export { useUseTitleField } from '../../hooks/useUseAsTitle.js'
|
||||
|
||||
// elements
|
||||
export { ConfirmationModal } from '../../elements/ConfirmationModal/index.js'
|
||||
export type { OnCancel } from '../../elements/ConfirmationModal/index.js'
|
||||
export type { OnCancel, OnConfirm } from '../../elements/ConfirmationModal/index.js'
|
||||
export { Link } from '../../elements/Link/index.js'
|
||||
export { LeaveWithoutSaving } from '../../elements/LeaveWithoutSaving/index.js'
|
||||
export { DocumentTakeOver } from '../../elements/DocumentTakeOver/index.js'
|
||||
|
||||
@@ -38,8 +38,6 @@ const DateTimeFieldComponent: DateFieldClientComponent = (props) => {
|
||||
validate,
|
||||
} = props
|
||||
|
||||
const pickerAppearance = datePickerProps?.pickerAppearance || 'default'
|
||||
|
||||
// Get the user timezone so we can adjust the displayed value against it
|
||||
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
|
||||
@@ -61,7 +59,7 @@ const DateTimeFieldComponent: DateFieldClientComponent = (props) => {
|
||||
setValue,
|
||||
showError,
|
||||
value,
|
||||
} = useField<string>({
|
||||
} = useField<Date>({
|
||||
path,
|
||||
validate: memoizedValidate,
|
||||
})
|
||||
@@ -69,11 +67,7 @@ const DateTimeFieldComponent: DateFieldClientComponent = (props) => {
|
||||
const timezonePath = path + '_tz'
|
||||
const timezoneField = useFormFields(([fields, _]) => fields?.[timezonePath])
|
||||
const supportedTimezones = config.admin.timezones.supportedTimezones
|
||||
/**
|
||||
* Date appearance doesn't include timestamps,
|
||||
* which means we need to pin the time to always 12:00 for the selected date
|
||||
*/
|
||||
const isDateOnly = ['dayOnly', 'default', 'monthOnly'].includes(pickerAppearance)
|
||||
|
||||
const selectedTimezone = timezoneField?.value as string
|
||||
|
||||
// The displayed value should be the original value, adjusted to the user's timezone
|
||||
@@ -105,28 +99,15 @@ const DateTimeFieldComponent: DateFieldClientComponent = (props) => {
|
||||
if (!readOnly) {
|
||||
if (timezone && selectedTimezone && incomingDate) {
|
||||
// Create TZDate instances for the selected timezone
|
||||
const TZDateWithSelectedTz = TZDate.tz(selectedTimezone)
|
||||
const tzDateWithUTC = TZDate.tz(selectedTimezone)
|
||||
|
||||
if (isDateOnly) {
|
||||
// We need to offset this hardcoded hour offset from the DatePicker elemenent
|
||||
// this can be removed in 4.0 when we remove the hardcoded offset as it is a breaking change
|
||||
// const tzOffset = incomingDate.getTimezoneOffset() / 60
|
||||
const incomingOffset = incomingDate.getTimezoneOffset() / 60
|
||||
const originalHour = incomingDate.getHours() + incomingOffset
|
||||
incomingDate.setHours(originalHour)
|
||||
// Creates a TZDate instance for the user's timezone — this is default behaviour of TZDate as it wraps the Date constructor
|
||||
const dateToUserTz = new TZDate(incomingDate)
|
||||
|
||||
// Convert the original date as picked into the desired timezone.
|
||||
const dateToSelectedTz = transpose(incomingDate, TZDateWithSelectedTz)
|
||||
// Transpose the date to the selected timezone
|
||||
const dateWithTimezone = transpose(dateToUserTz, tzDateWithUTC)
|
||||
|
||||
setValue(dateToSelectedTz.toISOString() || null)
|
||||
} else {
|
||||
// Creates a TZDate instance for the user's timezone — this is default behaviour of TZDate as it wraps the Date constructor
|
||||
const dateToUserTz = new TZDate(incomingDate)
|
||||
// Transpose the date to the selected timezone
|
||||
const dateWithTimezone = transpose(dateToUserTz, TZDateWithSelectedTz)
|
||||
|
||||
setValue(dateWithTimezone.toISOString() || null)
|
||||
}
|
||||
setValue(dateWithTimezone.toISOString() || null)
|
||||
} else {
|
||||
setValue(incomingDate?.toISOString() || null)
|
||||
}
|
||||
@@ -192,6 +173,7 @@ const DateTimeFieldComponent: DateFieldClientComponent = (props) => {
|
||||
selectedTimezone={selectedTimezone}
|
||||
/>
|
||||
)}
|
||||
|
||||
{AfterInput}
|
||||
</div>
|
||||
<RenderCustomComponent
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
@extend %h4;
|
||||
display: flex;
|
||||
padding-bottom: base(1);
|
||||
margin: 0;
|
||||
margin-inline-end: $baseline;
|
||||
margin: 0 $baseline 0 0;
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
position: relative;
|
||||
|
||||
@@ -43,6 +43,7 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
const modified = useFormModified()
|
||||
const { i18n, t } = useTranslation()
|
||||
const { docPermissions, isEditing, isInitializing } = useDocumentInfo()
|
||||
|
||||
const {
|
||||
config: {
|
||||
routes: { api },
|
||||
@@ -50,11 +51,15 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
},
|
||||
} = useConfig()
|
||||
|
||||
const enableFields =
|
||||
!disableLocalStrategy ||
|
||||
(typeof disableLocalStrategy === 'object' && disableLocalStrategy.enableFields === true)
|
||||
const hasPermissionToUnlock: boolean = useMemo(() => {
|
||||
const collection = permissions?.collections?.[collectionSlug]
|
||||
|
||||
const disabled = readOnly || isInitializing
|
||||
if (collection) {
|
||||
return Boolean('unlock' in collection ? collection.unlock : undefined)
|
||||
}
|
||||
|
||||
return false
|
||||
}, [permissions, collectionSlug])
|
||||
|
||||
const apiKeyPermissions =
|
||||
docPermissions?.fields === true ? true : docPermissions?.fields?.enableAPIKey
|
||||
@@ -69,16 +74,6 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
|
||||
const canReadApiKey = apiKeyPermissions === true || apiKeyPermissions?.read
|
||||
|
||||
const hasPermissionToUnlock: boolean = useMemo(() => {
|
||||
const collection = permissions?.collections?.[collectionSlug]
|
||||
|
||||
if (collection) {
|
||||
return Boolean('unlock' in collection ? collection.unlock : undefined)
|
||||
}
|
||||
|
||||
return false
|
||||
}, [permissions, collectionSlug])
|
||||
|
||||
const handleChangePassword = useCallback(
|
||||
(showPasswordFields: boolean) => {
|
||||
if (showPasswordFields) {
|
||||
@@ -134,13 +129,15 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
}
|
||||
}, [modified])
|
||||
|
||||
if (disableLocalStrategy && !enableFields && !useAPIKey) {
|
||||
if (disableLocalStrategy && !useAPIKey) {
|
||||
return null
|
||||
}
|
||||
|
||||
const disabled = readOnly || isInitializing
|
||||
|
||||
return (
|
||||
<div className={[baseClass, className].filter(Boolean).join(' ')}>
|
||||
{enableFields && (
|
||||
{!disableLocalStrategy && (
|
||||
<React.Fragment>
|
||||
<EmailAndUsernameFields
|
||||
loginWithUsername={loginWithUsername}
|
||||
@@ -149,7 +146,7 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
readOnly={readOnly}
|
||||
t={t}
|
||||
/>
|
||||
{(changingPassword || requirePassword) && (!disableLocalStrategy || !enableFields) && (
|
||||
{(changingPassword || requirePassword) && (
|
||||
<div className={`${baseClass}__changing-password`}>
|
||||
<PasswordField
|
||||
autoComplete="new-password"
|
||||
@@ -178,7 +175,7 @@ export const Auth: React.FC<Props> = (props) => {
|
||||
{t('general:cancel')}
|
||||
</Button>
|
||||
)}
|
||||
{!changingPassword && !requirePassword && !disableLocalStrategy && (
|
||||
{!changingPassword && !requirePassword && (
|
||||
<Button
|
||||
buttonStyle="secondary"
|
||||
disabled={disabled}
|
||||
|
||||
53
pnpm-lock.yaml
generated
53
pnpm-lock.yaml
generated
@@ -716,6 +716,9 @@ importers:
|
||||
sass:
|
||||
specifier: 1.77.4
|
||||
version: 1.77.4
|
||||
sonner:
|
||||
specifier: ^1.7.0
|
||||
version: 1.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
|
||||
uuid:
|
||||
specifier: 10.0.0
|
||||
version: 10.0.0
|
||||
@@ -3655,79 +3658,67 @@ packages:
|
||||
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.0.5':
|
||||
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.0.4':
|
||||
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.0.4':
|
||||
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
|
||||
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
|
||||
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linux-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-arm@0.33.5':
|
||||
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-s390x@0.33.5':
|
||||
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-x64@0.33.5':
|
||||
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.33.5':
|
||||
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.33.5':
|
||||
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-wasm32@0.33.5':
|
||||
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
|
||||
@@ -4043,49 +4034,42 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/nice-linux-arm64-musl@1.0.1':
|
||||
resolution: {integrity: sha512-wG8fa2VKuWM4CfjOjjRX9YLIbysSVV1S3Kgm2Fnc67ap/soHBeYZa6AGMeR5BJAylYRjnoVOzV19Cmkco3QEPw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@napi-rs/nice-linux-ppc64-gnu@1.0.1':
|
||||
resolution: {integrity: sha512-lxQ9WrBf0IlNTCA9oS2jg/iAjQyTI6JHzABV664LLrLA/SIdD+I1i3Mjf7TsnoUbgopBcCuDztVLfJ0q9ubf6Q==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/nice-linux-riscv64-gnu@1.0.1':
|
||||
resolution: {integrity: sha512-3xs69dO8WSWBb13KBVex+yvxmUeEsdWexxibqskzoKaWx9AIqkMbWmE2npkazJoopPKX2ULKd8Fm9veEn0g4Ig==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/nice-linux-s390x-gnu@1.0.1':
|
||||
resolution: {integrity: sha512-lMFI3i9rlW7hgToyAzTaEybQYGbQHDrpRkg+1gJWEpH0PLAQoZ8jiY0IzakLfNWnVda1eTYYlxxFYzW8Rqczkg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/nice-linux-x64-gnu@1.0.1':
|
||||
resolution: {integrity: sha512-XQAJs7DRN2GpLN6Fb+ZdGFeYZDdGl2Fn3TmFlqEL5JorgWKrQGRUrpGKbgZ25UeZPILuTKJ+OowG2avN8mThBA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@napi-rs/nice-linux-x64-musl@1.0.1':
|
||||
resolution: {integrity: sha512-/rodHpRSgiI9o1faq9SZOp/o2QkKQg7T+DK0R5AkbnI/YxvAIEHf2cngjYzLMQSQgUhxym+LFr+UGZx4vK4QdQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@napi-rs/nice-win32-arm64-msvc@1.0.1':
|
||||
resolution: {integrity: sha512-rEcz9vZymaCB3OqEXoHnp9YViLct8ugF+6uO5McifTedjq4QMQs3DHz35xBEGhH3gJWEsXMUbzazkz5KNM5YUg==}
|
||||
@@ -4174,84 +4158,72 @@ packages:
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@15.1.3':
|
||||
resolution: {integrity: sha512-YbdaYjyHa4fPK4GR4k2XgXV0p8vbU1SZh7vv6El4bl9N+ZSiMfbmqCuCuNU1Z4ebJMumafaz6UCC2zaJCsdzjw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-gnu@15.1.5':
|
||||
resolution: {integrity: sha512-rDJC4ctlYbK27tCyFUhgIv8o7miHNlpCjb2XXfTLQszwAUOSbcMN9q2y3urSrrRCyGVOd9ZR9a4S45dRh6JF3A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.0.3':
|
||||
resolution: {integrity: sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.1.3':
|
||||
resolution: {integrity: sha512-qgH/aRj2xcr4BouwKG3XdqNu33SDadqbkqB6KaZZkozar857upxKakbRllpqZgWl/NDeSCBYPmUAZPBHZpbA0w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-arm64-musl@15.1.5':
|
||||
resolution: {integrity: sha512-FG5RApf4Gu+J+pHUQxXPM81oORZrKBYKUaBTylEIQ6Lz17hKVDsLbSXInfXM0giclvXbyiLXjTv42sQMATmZ0A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.0.3':
|
||||
resolution: {integrity: sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.1.3':
|
||||
resolution: {integrity: sha512-uzafnTFwZCPN499fNVnS2xFME8WLC9y7PLRs/yqz5lz1X/ySoxfaK2Hbz74zYUdEg+iDZPd8KlsWaw9HKkLEVw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.1.5':
|
||||
resolution: {integrity: sha512-NX2Ar3BCquAOYpnoYNcKz14eH03XuF7SmSlPzTSSU4PJe7+gelAjxo3Y7F2m8+hLT8ZkkqElawBp7SWBdzwqQw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-musl@15.0.3':
|
||||
resolution: {integrity: sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-musl@15.1.3':
|
||||
resolution: {integrity: sha512-el6GUFi4SiDYnMTTlJJFMU+GHvw0UIFnffP1qhurrN1qJV3BqaSRUjkDUgVV44T6zpw1Lc6u+yn0puDKHs+Sbw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-musl@15.1.5':
|
||||
resolution: {integrity: sha512-EQgqMiNu3mrV5eQHOIgeuh6GB5UU57tu17iFnLfBEhYfiOfyK+vleYKh2dkRVkV6ayx3eSqbIYgE7J7na4hhcA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@15.0.3':
|
||||
resolution: {integrity: sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==}
|
||||
@@ -4538,25 +4510,21 @@ packages:
|
||||
resolution: {integrity: sha512-otVbS4zeo3n71zgGLBYRTriDzc0zpruC0WI3ICwjpIk454cLwGV0yzh4jlGYWQJYJk0BRAmXFd3ooKIF+bKBHw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-resolver/binding-linux-arm64-musl@1.12.0':
|
||||
resolution: {integrity: sha512-IStQDjIT7Lzmqg1i9wXvPL/NsYsxF24WqaQFS8b8rxra+z0VG7saBOsEnOaa4jcEY8MVpLYabFhTV+fSsA2vnA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-resolver/binding-linux-x64-gnu@1.12.0':
|
||||
resolution: {integrity: sha512-SipT7EVORz8pOQSFwemOm91TpSiBAGmOjG830/o+aLEsvQ4pEy223+SAnCfITh7+AahldYsJnVoIs519jmIlKQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@oxc-resolver/binding-linux-x64-musl@1.12.0':
|
||||
resolution: {integrity: sha512-mGh0XfUzKdn+WFaqPacziNraCWL5znkHRfQVxG9avGS9zb2KC/N1EBbPzFqutDwixGDP54r2gx4q54YCJEZ4iQ==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@oxc-resolver/binding-wasm32-wasi@1.12.0':
|
||||
resolution: {integrity: sha512-SZN6v7apKmQf/Vwiqb6e/s3Y2Oacw8uW8V2i1AlxtyaEFvnFE0UBn89zq6swEwE3OCajNWs0yPvgAXUMddYc7Q==}
|
||||
@@ -5044,28 +5012,24 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@swc/core-linux-arm64-musl@1.10.12':
|
||||
resolution: {integrity: sha512-oqhSmV+XauSf0C//MoQnVErNUB/5OzmSiUzuazyLsD5pwqKNN+leC3JtRQ/QVzaCpr65jv9bKexT9+I2Tt3xDw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@swc/core-linux-x64-gnu@1.10.12':
|
||||
resolution: {integrity: sha512-XldSIHyjD7m1Gh+/8rxV3Ok711ENLI420CU2EGEqSe3VSGZ7pHJvJn9ZFbYpWhsLxPqBYMFjp3Qw+J6OXCPXCA==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@swc/core-linux-x64-musl@1.10.12':
|
||||
resolution: {integrity: sha512-wvPXzJxzPgTqhyp1UskOx1hRTtdWxlyFD1cGWOxgLsMik0V9xKRgqKnMPv16Nk7L9xl6quQ6DuUHj9ID7L3oVw==}
|
||||
engines: {node: '>=10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@swc/core-win32-arm64-msvc@1.10.12':
|
||||
resolution: {integrity: sha512-TUYzWuu1O7uyIcRfxdm6Wh1u+gNnrW5M1DUgDOGZLsyQzgc2Zjwfh2llLhuAIilvCVg5QiGbJlpibRYJ/8QGsg==}
|
||||
@@ -9382,6 +9346,12 @@ packages:
|
||||
sonic-boom@4.2.0:
|
||||
resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==}
|
||||
|
||||
sonner@1.7.0:
|
||||
resolution: {integrity: sha512-W6dH7m5MujEPyug3lpI2l3TC3Pp1+LTgK0Efg+IHDrBbtEjyCmCHHo6yfNBOsf1tFZ6zf+jceWwB38baC8yO9g==}
|
||||
peerDependencies:
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0
|
||||
|
||||
sonner@1.7.2:
|
||||
resolution: {integrity: sha512-zMbseqjrOzQD1a93lxahm+qMGxWovdMxBlkTbbnZdNqVLt4j+amF9PQxUCL32WfztOFt9t9ADYkejAL3jF9iNA==}
|
||||
peerDependencies:
|
||||
@@ -19347,6 +19317,11 @@ snapshots:
|
||||
dependencies:
|
||||
atomic-sleep: 1.0.0
|
||||
|
||||
sonner@1.7.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
react-dom: 19.0.0(react@19.0.0)
|
||||
|
||||
sonner@1.7.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0):
|
||||
dependencies:
|
||||
react: 19.0.0
|
||||
|
||||
@@ -56,7 +56,7 @@ export const ArchiveBlock: React.FC<
|
||||
<div className="my-16" id={`block-${id}`}>
|
||||
{introContent && (
|
||||
<div className="container mb-16">
|
||||
<RichText className="ms-0 max-w-[48rem]" data={introContent} enableGutter={false} />
|
||||
<RichText className="ml-0 max-w-[48rem]" data={introContent} enableGutter={false} />
|
||||
</div>
|
||||
)}
|
||||
<CollectionArchive posts={posts} />
|
||||
|
||||
@@ -56,7 +56,7 @@ export const ArchiveBlock: React.FC<
|
||||
<div className="my-16" id={`block-${id}`}>
|
||||
{introContent && (
|
||||
<div className="container mb-16">
|
||||
<RichText className="ms-0 max-w-[48rem]" data={introContent} enableGutter={false} />
|
||||
<RichText className="ml-0 max-w-[48rem]" data={introContent} enableGutter={false} />
|
||||
</div>
|
||||
)}
|
||||
<CollectionArchive posts={posts} />
|
||||
|
||||
@@ -22,13 +22,14 @@ import {
|
||||
hiddenAccessSlug,
|
||||
hiddenFieldsSlug,
|
||||
nonAdminEmail,
|
||||
nonAdminUserEmail,
|
||||
nonAdminUserSlug,
|
||||
publicUserEmail,
|
||||
publicUsersSlug,
|
||||
readNotUpdateGlobalSlug,
|
||||
readOnlyGlobalSlug,
|
||||
readOnlySlug,
|
||||
relyOnRequestHeadersSlug,
|
||||
restrictedVersionsAdminPanelSlug,
|
||||
restrictedVersionsSlug,
|
||||
secondArrayText,
|
||||
siblingDataSlug,
|
||||
@@ -323,35 +324,6 @@ export default buildConfigWithDefaults(
|
||||
],
|
||||
versions: true,
|
||||
},
|
||||
{
|
||||
slug: restrictedVersionsAdminPanelSlug,
|
||||
access: {
|
||||
read: ({ req: { user } }) => {
|
||||
if (user) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
readVersions: () => {
|
||||
return {
|
||||
'version.hidden': {
|
||||
not_equals: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'hidden',
|
||||
type: 'checkbox',
|
||||
},
|
||||
],
|
||||
versions: true,
|
||||
},
|
||||
{
|
||||
slug: siblingDataSlug,
|
||||
access: openAccess,
|
||||
|
||||
@@ -35,7 +35,6 @@ import {
|
||||
readNotUpdateGlobalSlug,
|
||||
readOnlyGlobalSlug,
|
||||
readOnlySlug,
|
||||
restrictedVersionsAdminPanelSlug,
|
||||
restrictedVersionsSlug,
|
||||
slug,
|
||||
unrestrictedSlug,
|
||||
@@ -64,7 +63,6 @@ describe('Access Control', () => {
|
||||
let richTextUrl: AdminUrlUtil
|
||||
let readOnlyGlobalUrl: AdminUrlUtil
|
||||
let restrictedVersionsUrl: AdminUrlUtil
|
||||
let restrictedVersionsAdminPanelUrl: AdminUrlUtil
|
||||
let userRestrictedCollectionURL: AdminUrlUtil
|
||||
let userRestrictedGlobalURL: AdminUrlUtil
|
||||
let disabledFields: AdminUrlUtil
|
||||
@@ -83,7 +81,6 @@ describe('Access Control', () => {
|
||||
readOnlyCollectionUrl = new AdminUrlUtil(serverURL, readOnlySlug)
|
||||
readOnlyGlobalUrl = new AdminUrlUtil(serverURL, readOnlySlug)
|
||||
restrictedVersionsUrl = new AdminUrlUtil(serverURL, restrictedVersionsSlug)
|
||||
restrictedVersionsAdminPanelUrl = new AdminUrlUtil(serverURL, restrictedVersionsAdminPanelSlug)
|
||||
userRestrictedCollectionURL = new AdminUrlUtil(serverURL, userRestrictedCollectionSlug)
|
||||
userRestrictedGlobalURL = new AdminUrlUtil(serverURL, userRestrictedGlobalSlug)
|
||||
disabledFields = new AdminUrlUtil(serverURL, disabledSlug)
|
||||
@@ -560,26 +557,16 @@ describe('Access Control', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
existingDoc = await payload.create({
|
||||
collection: restrictedVersionsAdminPanelSlug,
|
||||
collection: restrictedVersionsSlug,
|
||||
data: {
|
||||
name: 'name',
|
||||
},
|
||||
})
|
||||
|
||||
await payload.update({
|
||||
collection: restrictedVersionsAdminPanelSlug,
|
||||
id: existingDoc.id,
|
||||
data: {
|
||||
hidden: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('versions tab should not show', async () => {
|
||||
await page.goto(restrictedVersionsAdminPanelUrl.edit(existingDoc.id))
|
||||
await page.locator('.doc-tabs__tabs').getByLabel('Versions').click()
|
||||
const rows = page.locator('.versions table tbody tr')
|
||||
await expect(rows).toHaveCount(1)
|
||||
test('versions sidebar should not show', async () => {
|
||||
await page.goto(restrictedVersionsUrl.edit(existingDoc.id))
|
||||
await expect(page.locator('.versions-count')).toBeHidden()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -6,11 +6,66 @@
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Supported timezones in IANA format.
|
||||
*
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "supportedTimezones".
|
||||
*/
|
||||
export type SupportedTimezones =
|
||||
| 'Pacific/Midway'
|
||||
| 'Pacific/Niue'
|
||||
| 'Pacific/Honolulu'
|
||||
| 'Pacific/Rarotonga'
|
||||
| 'America/Anchorage'
|
||||
| 'Pacific/Gambier'
|
||||
| 'America/Los_Angeles'
|
||||
| 'America/Tijuana'
|
||||
| 'America/Denver'
|
||||
| 'America/Phoenix'
|
||||
| 'America/Chicago'
|
||||
| 'America/Guatemala'
|
||||
| 'America/New_York'
|
||||
| 'America/Bogota'
|
||||
| 'America/Caracas'
|
||||
| 'America/Santiago'
|
||||
| 'America/Buenos_Aires'
|
||||
| 'America/Sao_Paulo'
|
||||
| 'Atlantic/South_Georgia'
|
||||
| 'Atlantic/Azores'
|
||||
| 'Atlantic/Cape_Verde'
|
||||
| 'Europe/London'
|
||||
| 'Europe/Berlin'
|
||||
| 'Africa/Lagos'
|
||||
| 'Europe/Athens'
|
||||
| 'Africa/Cairo'
|
||||
| 'Europe/Moscow'
|
||||
| 'Asia/Riyadh'
|
||||
| 'Asia/Dubai'
|
||||
| 'Asia/Baku'
|
||||
| 'Asia/Karachi'
|
||||
| 'Asia/Tashkent'
|
||||
| 'Asia/Calcutta'
|
||||
| 'Asia/Dhaka'
|
||||
| 'Asia/Almaty'
|
||||
| 'Asia/Jakarta'
|
||||
| 'Asia/Bangkok'
|
||||
| 'Asia/Shanghai'
|
||||
| 'Asia/Singapore'
|
||||
| 'Asia/Tokyo'
|
||||
| 'Asia/Seoul'
|
||||
| 'Australia/Sydney'
|
||||
| 'Pacific/Guam'
|
||||
| 'Pacific/Noumea'
|
||||
| 'Pacific/Auckland'
|
||||
| 'Pacific/Fiji';
|
||||
|
||||
export interface Config {
|
||||
auth: {
|
||||
users: UserAuthOperations;
|
||||
'public-users': PublicUserAuthOperations;
|
||||
};
|
||||
blocks: {};
|
||||
collections: {
|
||||
users: User;
|
||||
'public-users': PublicUser;
|
||||
@@ -22,7 +77,6 @@ export interface Config {
|
||||
'user-restricted-collection': UserRestrictedCollection;
|
||||
'create-not-update-collection': CreateNotUpdateCollection;
|
||||
'restricted-versions': RestrictedVersion;
|
||||
'restricted-versions-admin-panel': RestrictedVersionsAdminPanel;
|
||||
'sibling-data': SiblingDatum;
|
||||
'rely-on-request-headers': RelyOnRequestHeader;
|
||||
'doc-level-access': DocLevelAccess;
|
||||
@@ -50,7 +104,6 @@ export interface Config {
|
||||
'user-restricted-collection': UserRestrictedCollectionSelect<false> | UserRestrictedCollectionSelect<true>;
|
||||
'create-not-update-collection': CreateNotUpdateCollectionSelect<false> | CreateNotUpdateCollectionSelect<true>;
|
||||
'restricted-versions': RestrictedVersionsSelect<false> | RestrictedVersionsSelect<true>;
|
||||
'restricted-versions-admin-panel': RestrictedVersionsAdminPanelSelect<false> | RestrictedVersionsAdminPanelSelect<true>;
|
||||
'sibling-data': SiblingDataSelect<false> | SiblingDataSelect<true>;
|
||||
'rely-on-request-headers': RelyOnRequestHeadersSelect<false> | RelyOnRequestHeadersSelect<true>;
|
||||
'doc-level-access': DocLevelAccessSelect<false> | DocLevelAccessSelect<true>;
|
||||
@@ -256,17 +309,6 @@ export interface RestrictedVersion {
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "restricted-versions-admin-panel".
|
||||
*/
|
||||
export interface RestrictedVersionsAdminPanel {
|
||||
id: string;
|
||||
name?: string | null;
|
||||
hidden?: boolean | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "sibling-data".
|
||||
@@ -672,10 +714,6 @@ export interface PayloadLockedDocument {
|
||||
relationTo: 'restricted-versions';
|
||||
value: string | RestrictedVersion;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'restricted-versions-admin-panel';
|
||||
value: string | RestrictedVersionsAdminPanel;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'sibling-data';
|
||||
value: string | SiblingDatum;
|
||||
@@ -886,16 +924,6 @@ export interface RestrictedVersionsSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "restricted-versions-admin-panel_select".
|
||||
*/
|
||||
export interface RestrictedVersionsAdminPanelSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
hidden?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "sibling-data_select".
|
||||
|
||||
@@ -12,7 +12,6 @@ export const createNotUpdateCollectionSlug = 'create-not-update-collection'
|
||||
export const userRestrictedGlobalSlug = 'user-restricted-global'
|
||||
export const readNotUpdateGlobalSlug = 'read-not-update-global'
|
||||
export const restrictedVersionsSlug = 'restricted-versions'
|
||||
export const restrictedVersionsAdminPanelSlug = 'restricted-versions-admin-panel'
|
||||
export const siblingDataSlug = 'sibling-data'
|
||||
export const relyOnRequestHeadersSlug = 'rely-on-request-headers'
|
||||
export const docLevelAccessSlug = 'doc-level-access'
|
||||
|
||||
@@ -9,7 +9,7 @@ import { devUser } from '../credentials.js'
|
||||
import {
|
||||
apiKeysSlug,
|
||||
namedSaveToJWTValue,
|
||||
partialDisableLocalStrategiesSlug,
|
||||
partialDisableLocaleStrategiesSlug,
|
||||
publicUsersSlug,
|
||||
saveToJWTKey,
|
||||
slug,
|
||||
@@ -185,7 +185,7 @@ export default buildConfigWithDefaults({
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: partialDisableLocalStrategiesSlug,
|
||||
slug: partialDisableLocaleStrategiesSlug,
|
||||
auth: {
|
||||
disableLocalStrategy: {
|
||||
// optionalPassword: true,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { initPayloadInt } from '../helpers/initPayloadInt.js'
|
||||
import {
|
||||
apiKeysSlug,
|
||||
namedSaveToJWTValue,
|
||||
partialDisableLocalStrategiesSlug,
|
||||
partialDisableLocaleStrategiesSlug,
|
||||
publicUsersSlug,
|
||||
saveToJWTKey,
|
||||
slug,
|
||||
@@ -720,7 +720,7 @@ describe('Auth', () => {
|
||||
it('should allow create of a user with disableLocalStrategy', async () => {
|
||||
const email = 'test@example.com'
|
||||
const user = await payload.create({
|
||||
collection: partialDisableLocalStrategiesSlug,
|
||||
collection: partialDisableLocaleStrategiesSlug,
|
||||
data: {
|
||||
email,
|
||||
// password is not required
|
||||
@@ -730,7 +730,7 @@ describe('Auth', () => {
|
||||
})
|
||||
|
||||
it('should retain fields when auth.disableLocalStrategy.enableFields is true', () => {
|
||||
const authFields = payload.collections[partialDisableLocalStrategiesSlug].config.fields
|
||||
const authFields = payload.collections[partialDisableLocaleStrategiesSlug].config.fields
|
||||
// eslint-disable-next-line jest/no-conditional-in-test
|
||||
.filter((field) => 'name' in field && field.name)
|
||||
.map((field) => (field as FieldAffectingData).name)
|
||||
@@ -750,7 +750,7 @@ describe('Auth', () => {
|
||||
|
||||
it('should prevent login of user with disableLocalStrategy.', async () => {
|
||||
await payload.create({
|
||||
collection: partialDisableLocalStrategiesSlug,
|
||||
collection: partialDisableLocaleStrategiesSlug,
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
@@ -759,7 +759,7 @@ describe('Auth', () => {
|
||||
|
||||
await expect(async () => {
|
||||
await payload.login({
|
||||
collection: partialDisableLocalStrategiesSlug,
|
||||
collection: partialDisableLocaleStrategiesSlug,
|
||||
data: {
|
||||
email: devUser.email,
|
||||
password: devUser.password,
|
||||
@@ -769,7 +769,7 @@ describe('Auth', () => {
|
||||
})
|
||||
|
||||
it('rest - should prevent login', async () => {
|
||||
const response = await restClient.POST(`/${partialDisableLocalStrategiesSlug}/login`, {
|
||||
const response = await restClient.POST(`/${partialDisableLocaleStrategiesSlug}/login`, {
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
|
||||
@@ -63,14 +63,14 @@ export type SupportedTimezones =
|
||||
export interface Config {
|
||||
auth: {
|
||||
users: UserAuthOperations;
|
||||
'partial-disable-local-strategies': PartialDisableLocalStrategyAuthOperations;
|
||||
'partial-disable-locale-strategies': PartialDisableLocaleStrategyAuthOperations;
|
||||
'api-keys': ApiKeyAuthOperations;
|
||||
'public-users': PublicUserAuthOperations;
|
||||
};
|
||||
blocks: {};
|
||||
collections: {
|
||||
users: User;
|
||||
'partial-disable-local-strategies': PartialDisableLocalStrategy;
|
||||
'partial-disable-locale-strategies': PartialDisableLocaleStrategy;
|
||||
'api-keys': ApiKey;
|
||||
'public-users': PublicUser;
|
||||
relationsCollection: RelationsCollection;
|
||||
@@ -81,7 +81,7 @@ export interface Config {
|
||||
collectionsJoins: {};
|
||||
collectionsSelect: {
|
||||
users: UsersSelect<false> | UsersSelect<true>;
|
||||
'partial-disable-local-strategies': PartialDisableLocalStrategiesSelect<false> | PartialDisableLocalStrategiesSelect<true>;
|
||||
'partial-disable-locale-strategies': PartialDisableLocaleStrategiesSelect<false> | PartialDisableLocaleStrategiesSelect<true>;
|
||||
'api-keys': ApiKeysSelect<false> | ApiKeysSelect<true>;
|
||||
'public-users': PublicUsersSelect<false> | PublicUsersSelect<true>;
|
||||
relationsCollection: RelationsCollectionSelect<false> | RelationsCollectionSelect<true>;
|
||||
@@ -99,8 +99,8 @@ export interface Config {
|
||||
| (User & {
|
||||
collection: 'users';
|
||||
})
|
||||
| (PartialDisableLocalStrategy & {
|
||||
collection: 'partial-disable-local-strategies';
|
||||
| (PartialDisableLocaleStrategy & {
|
||||
collection: 'partial-disable-locale-strategies';
|
||||
})
|
||||
| (ApiKey & {
|
||||
collection: 'api-keys';
|
||||
@@ -131,7 +131,7 @@ export interface UserAuthOperations {
|
||||
password: string;
|
||||
};
|
||||
}
|
||||
export interface PartialDisableLocalStrategyAuthOperations {
|
||||
export interface PartialDisableLocaleStrategyAuthOperations {
|
||||
forgotPassword: {
|
||||
email: string;
|
||||
password: string;
|
||||
@@ -227,9 +227,9 @@ export interface User {
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "partial-disable-local-strategies".
|
||||
* via the `definition` "partial-disable-locale-strategies".
|
||||
*/
|
||||
export interface PartialDisableLocalStrategy {
|
||||
export interface PartialDisableLocaleStrategy {
|
||||
id: string;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
@@ -296,8 +296,8 @@ export interface PayloadLockedDocument {
|
||||
value: string | User;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'partial-disable-local-strategies';
|
||||
value: string | PartialDisableLocalStrategy;
|
||||
relationTo: 'partial-disable-locale-strategies';
|
||||
value: string | PartialDisableLocaleStrategy;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'api-keys';
|
||||
@@ -318,8 +318,8 @@ export interface PayloadLockedDocument {
|
||||
value: string | User;
|
||||
}
|
||||
| {
|
||||
relationTo: 'partial-disable-local-strategies';
|
||||
value: string | PartialDisableLocalStrategy;
|
||||
relationTo: 'partial-disable-locale-strategies';
|
||||
value: string | PartialDisableLocaleStrategy;
|
||||
}
|
||||
| {
|
||||
relationTo: 'api-keys';
|
||||
@@ -344,8 +344,8 @@ export interface PayloadPreference {
|
||||
value: string | User;
|
||||
}
|
||||
| {
|
||||
relationTo: 'partial-disable-local-strategies';
|
||||
value: string | PartialDisableLocalStrategy;
|
||||
relationTo: 'partial-disable-locale-strategies';
|
||||
value: string | PartialDisableLocaleStrategy;
|
||||
}
|
||||
| {
|
||||
relationTo: 'api-keys';
|
||||
@@ -427,9 +427,9 @@ export interface UsersSelect<T extends boolean = true> {
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "partial-disable-local-strategies_select".
|
||||
* via the `definition` "partial-disable-locale-strategies_select".
|
||||
*/
|
||||
export interface PartialDisableLocalStrategiesSelect<T extends boolean = true> {
|
||||
export interface PartialDisableLocaleStrategiesSelect<T extends boolean = true> {
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
email?: T;
|
||||
|
||||
@@ -4,7 +4,7 @@ export const publicUsersSlug = 'public-users'
|
||||
|
||||
export const apiKeysSlug = 'api-keys'
|
||||
|
||||
export const partialDisableLocalStrategiesSlug = 'partial-disable-local-strategies'
|
||||
export const partialDisableLocaleStrategiesSlug = 'partial-disable-locale-strategies'
|
||||
|
||||
export const namedSaveToJWTValue = 'namedSaveToJWT value'
|
||||
|
||||
|
||||
@@ -28,8 +28,6 @@ const dirname = path.resolve(currentFolder, '../../')
|
||||
const { beforeAll, beforeEach, describe } = test
|
||||
|
||||
const londonTimezone = 'Europe/London'
|
||||
const aucklandTimezone = 'Pacific/Auckland'
|
||||
const detroitTimezone = 'America/Detroit'
|
||||
|
||||
let payload: PayloadTestSDK<Config>
|
||||
let client: RESTClient
|
||||
@@ -254,6 +252,9 @@ describe('Date', () => {
|
||||
*
|
||||
* See: https://github.com/microsoft/playwright/issues/27138
|
||||
*/
|
||||
test.use({
|
||||
timezoneId: londonTimezone,
|
||||
})
|
||||
|
||||
test('should display the value in the selected time', async () => {
|
||||
const {
|
||||
@@ -495,851 +496,125 @@ describe('Date', () => {
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedUTCValue)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Date with TZ - Context: America/Detroit', () => {
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||
dirname,
|
||||
// prebuild,
|
||||
}))
|
||||
url = new AdminUrlUtil(serverURL, dateFieldsSlug)
|
||||
|
||||
const context = await browser.newContext({ timezoneId: detroitTimezone })
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
if (client) {
|
||||
await client.logout()
|
||||
}
|
||||
client = new RESTClient({ defaultSlug: 'users', serverURL })
|
||||
await client.login()
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
|
||||
test('displayed value should remain unchanged', async () => {
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
})
|
||||
|
||||
await page.goto(url.edit(existingDoc!.id))
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
return Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
})
|
||||
|
||||
await expect(() => {
|
||||
// Confirm that the emulated timezone is set to London
|
||||
expect(result).toEqual(detroitTimezone)
|
||||
}).toPass({ timeout: 10000, intervals: [100] })
|
||||
|
||||
const dateOnlyLocator = page.locator(
|
||||
'#field-defaultWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
const expectedDateOnlyValue = '08/12/2027'
|
||||
const expectedDateTimeValue = 'Aug 12, 2027 10:00 AM' // This is the seeded value for 10AM at Asia/Tokyo time
|
||||
|
||||
await expect(dateOnlyLocator).toHaveValue(expectedDateOnlyValue)
|
||||
await expect(dateTimeLocator).toHaveValue(expectedDateTimeValue)
|
||||
})
|
||||
|
||||
test('creates the expected UTC value when the selected timezone is Paris - no daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateInput = 'Jan 1, 2025 6:00 PM'
|
||||
// We're testing this specific date because Paris has no daylight savings time in January
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedUTCValue = '2025-01-01T17:00:00.000Z'
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Paris")`
|
||||
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedUTCValue)
|
||||
})
|
||||
|
||||
test('creates the expected UTC value when the selected timezone is Paris - with daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateInput = 'Jul 1, 2025 6:00 PM'
|
||||
|
||||
// We're testing specific date because Paris has daylight savings time in July (+1 hour to the local timezone)
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedUTCValue = '2025-07-01T16:00:00.000Z'
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Paris")`
|
||||
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedUTCValue)
|
||||
})
|
||||
|
||||
test('creates the expected UTC value when the selected timezone is Auckland - no daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateTimeInput = 'Jan 1, 2025 6:00 PM'
|
||||
// The timestamp for this date should be normalised to 12PM local time
|
||||
const expectedDateOnlyInput = '01/02/2025' // 2nd July 2025
|
||||
|
||||
// We're testing specific date because Paris has daylight savings time in July (+1 hour to the local timezone)
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedDateTimeUTCValue = '2025-01-01T05:00:00.000Z'
|
||||
// The timestamp for this date should be normalised to 12PM local time
|
||||
const expectedDateOnlyUTCValue = '2025-01-01T23:00:00.000Z' // 2nd July 2025 at 12PM in Auckland
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
// Default date field - filling it because it's required for the form to be valid
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
// Date input fields
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
const dateOnlyLocator = page.locator(
|
||||
'#field-defaultWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
// Fill in date only
|
||||
const dateOnlyDropdownSelector = `#field-defaultWithTimezone .rs__control`
|
||||
const dateOnlytimezoneSelector = `#field-defaultWithTimezone .rs__menu .rs__option:has-text("Auckland")`
|
||||
await page.click(dateOnlyDropdownSelector)
|
||||
await page.click(dateOnlytimezoneSelector)
|
||||
await dateOnlyLocator.fill(expectedDateOnlyInput)
|
||||
|
||||
// Fill in date and time
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Auckland")`
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateTimeInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedDateTimeUTCValue)
|
||||
expect(existingDoc?.defaultWithTimezone).toEqual(expectedDateOnlyUTCValue)
|
||||
})
|
||||
|
||||
test('creates the expected UTC value when the selected timezone is Auckland - with daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateTimeInput = 'Jul 1, 2025 6:00 PM'
|
||||
// The timestamp for this date should be normalised to 12PM local time
|
||||
const expectedDateOnlyInput = '07/02/2025' // 2nd July 2025
|
||||
|
||||
// We're testing specific date because Paris has daylight savings time in July (+1 hour to the local timezone)
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedDateTimeUTCValue = '2025-07-01T06:00:00.000Z'
|
||||
// The timestamp for this date should be normalised to 12PM local time
|
||||
const expectedDateOnlyUTCValue = '2025-07-02T00:00:00.000Z' // 2nd July 2025 at 12PM in Auckland
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
// Default date field - filling it because it's required for the form to be valid
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
// Date input fields
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
const dateOnlyLocator = page.locator(
|
||||
'#field-defaultWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
// Fill in date only
|
||||
const dateOnlyDropdownSelector = `#field-defaultWithTimezone .rs__control`
|
||||
const dateOnlytimezoneSelector = `#field-defaultWithTimezone .rs__menu .rs__option:has-text("Auckland")`
|
||||
await page.click(dateOnlyDropdownSelector)
|
||||
await page.click(dateOnlytimezoneSelector)
|
||||
await dateOnlyLocator.fill(expectedDateOnlyInput)
|
||||
|
||||
// Fill in date and time
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Auckland")`
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateTimeInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedDateTimeUTCValue)
|
||||
expect(existingDoc?.defaultWithTimezone).toEqual(expectedDateOnlyUTCValue)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Date with TZ - Context: Europe/London', () => {
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||
dirname,
|
||||
// prebuild,
|
||||
}))
|
||||
url = new AdminUrlUtil(serverURL, dateFieldsSlug)
|
||||
|
||||
const context = await browser.newContext({ timezoneId: londonTimezone })
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
if (client) {
|
||||
await client.logout()
|
||||
}
|
||||
client = new RESTClient({ defaultSlug: 'users', serverURL })
|
||||
await client.login()
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
|
||||
test('displayed value should remain unchanged', async () => {
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
})
|
||||
|
||||
await page.goto(url.edit(existingDoc!.id))
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
return Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
})
|
||||
|
||||
await expect(() => {
|
||||
// Confirm that the emulated timezone is set to London
|
||||
expect(result).toEqual(londonTimezone)
|
||||
}).toPass({ timeout: 10000, intervals: [100] })
|
||||
|
||||
const dateOnlyLocator = page.locator(
|
||||
'#field-defaultWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
const expectedDateOnlyValue = '08/12/2027'
|
||||
const expectedDateTimeValue = 'Aug 12, 2027 10:00 AM' // This is the seeded value for 10AM at Asia/Tokyo time
|
||||
|
||||
await expect(dateOnlyLocator).toHaveValue(expectedDateOnlyValue)
|
||||
await expect(dateTimeLocator).toHaveValue(expectedDateTimeValue)
|
||||
})
|
||||
|
||||
test('creates the expected UTC value when the selected timezone is Paris - no daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateInput = 'Jan 1, 2025 6:00 PM'
|
||||
// We're testing this specific date because Paris has no daylight savings time in January
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedUTCValue = '2025-01-01T17:00:00.000Z'
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Paris")`
|
||||
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedUTCValue)
|
||||
})
|
||||
|
||||
test('creates the expected UTC value when the selected timezone is Paris - with daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateInput = 'Jul 1, 2025 6:00 PM'
|
||||
|
||||
// We're testing specific date because Paris has daylight savings time in July (+1 hour to the local timezone)
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedUTCValue = '2025-07-01T16:00:00.000Z'
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Paris")`
|
||||
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedUTCValue)
|
||||
})
|
||||
|
||||
test('creates the expected UTC value when the selected timezone is Auckland - no daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateTimeInput = 'Jan 1, 2025 6:00 PM'
|
||||
// The timestamp for this date should be normalised to 12PM local time
|
||||
const expectedDateOnlyInput = '01/02/2025' // 2nd July 2025
|
||||
|
||||
// We're testing specific date because Paris has daylight savings time in July (+1 hour to the local timezone)
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedDateTimeUTCValue = '2025-01-01T05:00:00.000Z'
|
||||
// The timestamp for this date should be normalised to 12PM local time
|
||||
const expectedDateOnlyUTCValue = '2025-01-01T23:00:00.000Z' // 2nd July 2025 at 12PM in Auckland
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
// Default date field - filling it because it's required for the form to be valid
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
// Date input fields
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
const dateOnlyLocator = page.locator(
|
||||
'#field-defaultWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
// Fill in date only
|
||||
const dateOnlyDropdownSelector = `#field-defaultWithTimezone .rs__control`
|
||||
const dateOnlytimezoneSelector = `#field-defaultWithTimezone .rs__menu .rs__option:has-text("Auckland")`
|
||||
await page.click(dateOnlyDropdownSelector)
|
||||
await page.click(dateOnlytimezoneSelector)
|
||||
await dateOnlyLocator.fill(expectedDateOnlyInput)
|
||||
|
||||
// Fill in date and time
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Auckland")`
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateTimeInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedDateTimeUTCValue)
|
||||
expect(existingDoc?.defaultWithTimezone).toEqual(expectedDateOnlyUTCValue)
|
||||
})
|
||||
|
||||
test('creates the expected UTC value when the selected timezone is Auckland - with daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateTimeInput = 'Jul 1, 2025 6:00 PM'
|
||||
// The timestamp for this date should be normalised to 12PM local time
|
||||
const expectedDateOnlyInput = '07/02/2025' // 2nd July 2025
|
||||
|
||||
// We're testing specific date because Paris has daylight savings time in July (+1 hour to the local timezone)
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedDateTimeUTCValue = '2025-07-01T06:00:00.000Z'
|
||||
// The timestamp for this date should be normalised to 12PM local time
|
||||
const expectedDateOnlyUTCValue = '2025-07-02T00:00:00.000Z' // 2nd July 2025 at 12PM in Auckland
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
// Default date field - filling it because it's required for the form to be valid
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
// Date input fields
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
const dateOnlyLocator = page.locator(
|
||||
'#field-defaultWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
// Fill in date only
|
||||
const dateOnlyDropdownSelector = `#field-defaultWithTimezone .rs__control`
|
||||
const dateOnlytimezoneSelector = `#field-defaultWithTimezone .rs__menu .rs__option:has-text("Auckland")`
|
||||
await page.click(dateOnlyDropdownSelector)
|
||||
await page.click(dateOnlytimezoneSelector)
|
||||
await dateOnlyLocator.fill(expectedDateOnlyInput)
|
||||
|
||||
// Fill in date and time
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Auckland")`
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateTimeInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedDateTimeUTCValue)
|
||||
expect(existingDoc?.defaultWithTimezone).toEqual(expectedDateOnlyUTCValue)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Date with TZ - Context: Pacific/Auckland', () => {
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||
process.env.SEED_IN_CONFIG_ONINIT = 'false' // Makes it so the payload config onInit seed is not run. Otherwise, the seed would be run unnecessarily twice for the initial test run - once for beforeEach and once for onInit
|
||||
;({ payload, serverURL } = await initPayloadE2ENoConfig<Config>({
|
||||
dirname,
|
||||
// prebuild,
|
||||
}))
|
||||
url = new AdminUrlUtil(serverURL, dateFieldsSlug)
|
||||
|
||||
const context = await browser.newContext({ timezoneId: aucklandTimezone })
|
||||
page = await context.newPage()
|
||||
initPageConsoleErrorCatch(page)
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
beforeEach(async () => {
|
||||
await reInitializeDB({
|
||||
serverURL,
|
||||
snapshotKey: 'fieldsTest',
|
||||
uploadsDir: path.resolve(dirname, './collections/Upload/uploads'),
|
||||
})
|
||||
|
||||
if (client) {
|
||||
await client.logout()
|
||||
}
|
||||
client = new RESTClient({ defaultSlug: 'users', serverURL })
|
||||
await client.login()
|
||||
|
||||
await ensureCompilationIsDone({ page, serverURL })
|
||||
})
|
||||
|
||||
test('displayed value should remain unchanged', async () => {
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
})
|
||||
|
||||
await page.goto(url.edit(existingDoc!.id))
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
return Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
})
|
||||
|
||||
await expect(() => {
|
||||
// Confirm that the emulated timezone is set to London
|
||||
expect(result).toEqual(aucklandTimezone)
|
||||
}).toPass({ timeout: 10000, intervals: [100] })
|
||||
|
||||
const dateOnlyLocator = page.locator(
|
||||
'#field-defaultWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
const expectedDateOnlyValue = '08/12/2027'
|
||||
const expectedDateTimeValue = 'Aug 12, 2027 10:00 AM' // This is the seeded value for 10AM at Asia/Tokyo time
|
||||
|
||||
await expect(dateOnlyLocator).toHaveValue(expectedDateOnlyValue)
|
||||
await expect(dateTimeLocator).toHaveValue(expectedDateTimeValue)
|
||||
})
|
||||
|
||||
test('creates the expected UTC value when the selected timezone is Paris - no daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateInput = 'Jan 1, 2025 6:00 PM'
|
||||
// We're testing this specific date because Paris has no daylight savings time in January
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedUTCValue = '2025-01-01T17:00:00.000Z'
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Paris")`
|
||||
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedUTCValue)
|
||||
})
|
||||
|
||||
test('creates the expected UTC value when the selected timezone is Paris - with daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateInput = 'Jul 1, 2025 6:00 PM'
|
||||
|
||||
// We're testing specific date because Paris has daylight savings time in July (+1 hour to the local timezone)
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedUTCValue = '2025-07-01T16:00:00.000Z'
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Paris")`
|
||||
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedUTCValue)
|
||||
})
|
||||
|
||||
test('creates the expected UTC value when the selected timezone is Auckland - no daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateTimeInput = 'Jan 1, 2025 6:00 PM'
|
||||
// The timestamp for this date should be normalised to 12PM local time
|
||||
const expectedDateOnlyInput = '01/02/2025' // 2nd July 2025
|
||||
|
||||
// We're testing specific date because Paris has daylight savings time in July (+1 hour to the local timezone)
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedDateTimeUTCValue = '2025-01-01T05:00:00.000Z'
|
||||
// The timestamp for this date should be normalised to 12PM local time
|
||||
const expectedDateOnlyUTCValue = '2025-01-01T23:00:00.000Z' // 2nd July 2025 at 12PM in Auckland
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
// Default date field - filling it because it's required for the form to be valid
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
// Date input fields
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
const dateOnlyLocator = page.locator(
|
||||
'#field-defaultWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
// Fill in date only
|
||||
const dateOnlyDropdownSelector = `#field-defaultWithTimezone .rs__control`
|
||||
const dateOnlytimezoneSelector = `#field-defaultWithTimezone .rs__menu .rs__option:has-text("Auckland")`
|
||||
await page.click(dateOnlyDropdownSelector)
|
||||
await page.click(dateOnlytimezoneSelector)
|
||||
await dateOnlyLocator.fill(expectedDateOnlyInput)
|
||||
|
||||
// Fill in date and time
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Auckland")`
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateTimeInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedDateTimeUTCValue)
|
||||
expect(existingDoc?.defaultWithTimezone).toEqual(expectedDateOnlyUTCValue)
|
||||
})
|
||||
|
||||
test('creates the expected UTC value when the selected timezone is Auckland - with daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateTimeInput = 'Jul 1, 2025 6:00 PM'
|
||||
// The timestamp for this date should be normalised to 12PM local time
|
||||
const expectedDateOnlyInput = '07/02/2025' // 2nd July 2025
|
||||
|
||||
// We're testing specific date because Paris has daylight savings time in July (+1 hour to the local timezone)
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedDateTimeUTCValue = '2025-07-01T06:00:00.000Z'
|
||||
// The timestamp for this date should be normalised to 12PM local time
|
||||
const expectedDateOnlyUTCValue = '2025-07-02T00:00:00.000Z' // 2nd July 2025 at 12PM in Auckland
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
// Default date field - filling it because it's required for the form to be valid
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
// Date input fields
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
const dateOnlyLocator = page.locator(
|
||||
'#field-defaultWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
// Fill in date only
|
||||
const dateOnlyDropdownSelector = `#field-defaultWithTimezone .rs__control`
|
||||
const dateOnlytimezoneSelector = `#field-defaultWithTimezone .rs__menu .rs__option:has-text("Auckland")`
|
||||
await page.click(dateOnlyDropdownSelector)
|
||||
await page.click(dateOnlytimezoneSelector)
|
||||
await dateOnlyLocator.fill(expectedDateOnlyInput)
|
||||
|
||||
// Fill in date and time
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Auckland")`
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateTimeInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedDateTimeUTCValue)
|
||||
expect(existingDoc?.defaultWithTimezone).toEqual(expectedDateOnlyUTCValue)
|
||||
|
||||
test('creates the expected UTC value when the timezone is Paris - no daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateInput = 'Jan 1, 2025 6:00 PM'
|
||||
// We're testing this specific date because Paris has no daylight savings time in January
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedUTCValue = '2025-01-01T17:00:00.000Z'
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Paris")`
|
||||
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedUTCValue)
|
||||
})
|
||||
|
||||
test('creates the expected UTC value when the timezone is Paris - with daylight savings', async () => {
|
||||
// We send this value through the input
|
||||
const expectedDateInput = 'Jul 1, 2025 6:00 PM'
|
||||
|
||||
// We're testing specific date because Paris has daylight savings time in July (+1 hour to the local timezone)
|
||||
// but the UTC date will be different from 6PM local time in the summer versus the winter
|
||||
const expectedUTCValue = '2025-07-01T16:00:00.000Z'
|
||||
|
||||
await page.goto(url.create)
|
||||
|
||||
const dateField = page.locator('#field-default input')
|
||||
await dateField.fill('01/01/2025')
|
||||
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
const dropdownControlSelector = `#field-dayAndTimeWithTimezone .rs__control`
|
||||
const timezoneOptionSelector = `#field-dayAndTimeWithTimezone .rs__menu .rs__option:has-text("Paris")`
|
||||
|
||||
await page.click(dropdownControlSelector)
|
||||
await page.click(timezoneOptionSelector)
|
||||
await dateTimeLocator.fill(expectedDateInput)
|
||||
|
||||
await saveDocAndAssert(page)
|
||||
|
||||
const docID = page.url().split('/').pop()
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(docID).toBeTruthy()
|
||||
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
where: {
|
||||
id: {
|
||||
equals: docID,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// eslint-disable-next-line payload/no-flaky-assertions
|
||||
expect(existingDoc?.dayAndTimeWithTimezone).toEqual(expectedUTCValue)
|
||||
})
|
||||
|
||||
describe('while timezone is set to London', () => {
|
||||
test('displayed value should be the same while timezone is set to London', async () => {
|
||||
const {
|
||||
docs: [existingDoc],
|
||||
} = await payload.find({
|
||||
collection: dateFieldsSlug,
|
||||
})
|
||||
|
||||
await page.goto(url.edit(existingDoc!.id))
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
return Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
})
|
||||
|
||||
await expect(() => {
|
||||
// Confirm that the emulated timezone is set to London
|
||||
expect(result).toEqual(londonTimezone)
|
||||
}).toPass({ timeout: 10000, intervals: [100] })
|
||||
|
||||
const dateTimeLocator = page.locator(
|
||||
'#field-dayAndTimeWithTimezone .react-datepicker-wrapper input',
|
||||
)
|
||||
|
||||
const expectedValue = 'Aug 12, 2027 10:00 AM' // This is the seeded value for 10AM at Asia/Tokyo time
|
||||
|
||||
await expect(dateTimeLocator).toHaveValue(expectedValue)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -22,38 +22,6 @@ export default buildConfigWithDefaults({
|
||||
type: 'text',
|
||||
name: 'title',
|
||||
},
|
||||
{
|
||||
name: 'selectField',
|
||||
type: 'select',
|
||||
required: true,
|
||||
interfaceName: 'MySelectOptions',
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: 'option-1',
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: 'option-2',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'radioField',
|
||||
type: 'radio',
|
||||
required: true,
|
||||
interfaceName: 'MyRadioOptions',
|
||||
options: [
|
||||
{
|
||||
label: 'Option 1',
|
||||
value: 'option-1',
|
||||
},
|
||||
{
|
||||
label: 'Option 2',
|
||||
value: 'option-2',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,16 +6,6 @@
|
||||
* and re-run `payload generate:types` to regenerate this file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "MySelectOptions".
|
||||
*/
|
||||
export type MySelectOptions = 'option-1' | 'option-2';
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "MyRadioOptions".
|
||||
*/
|
||||
export type MyRadioOptions = 'option-1' | 'option-2';
|
||||
/**
|
||||
* Supported timezones in IANA format.
|
||||
*
|
||||
@@ -142,8 +132,6 @@ export interface Post {
|
||||
id: string;
|
||||
text?: string | null;
|
||||
title?: string | null;
|
||||
selectField: MySelectOptions;
|
||||
radioField: MyRadioOptions;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -261,8 +249,6 @@ export interface PayloadMigration {
|
||||
export interface PostsSelect<T extends boolean = true> {
|
||||
text?: T;
|
||||
title?: T;
|
||||
selectField?: T;
|
||||
radioField?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
|
||||
@@ -10,14 +10,7 @@ import type {
|
||||
import payload from 'payload'
|
||||
import { describe, expect, test } from 'tstyche'
|
||||
|
||||
import type {
|
||||
Menu,
|
||||
MyRadioOptions,
|
||||
MySelectOptions,
|
||||
Post,
|
||||
SupportedTimezones,
|
||||
User,
|
||||
} from './payload-types.js'
|
||||
import type { Menu, Post, User } from './payload-types.js'
|
||||
|
||||
const asType = <T>() => {
|
||||
return '' as T
|
||||
@@ -131,18 +124,4 @@ describe('Types testing', () => {
|
||||
}>()
|
||||
})
|
||||
})
|
||||
|
||||
describe('generated types', () => {
|
||||
test('has SupportedTimezones', () => {
|
||||
expect<SupportedTimezones>().type.toBeAssignableTo<string>()
|
||||
})
|
||||
|
||||
test('has global generated options interface based on select field', () => {
|
||||
expect(asType<Post['selectField']>()).type.toBe<MySelectOptions>()
|
||||
})
|
||||
|
||||
test('has global generated options interface based on radio field', () => {
|
||||
expect(asType<Post['radioField']>()).type.toBe<MyRadioOptions>()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -743,49 +743,6 @@ describe('Versions', () => {
|
||||
expect(versionsTabUpdated).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Scheduled publish', () => {
|
||||
beforeAll(() => {
|
||||
url = new AdminUrlUtil(serverURL, draftCollectionSlug)
|
||||
})
|
||||
|
||||
test('should schedule publish', async () => {
|
||||
await page.goto(url.create)
|
||||
await page.waitForURL(url.create)
|
||||
await page.locator('#field-title').fill('scheduled publish')
|
||||
await page.locator('#field-description').fill('scheduled publish description')
|
||||
|
||||
// schedule publish should not be available before document has been saved
|
||||
await page.locator('#action-save-popup').click()
|
||||
await expect(page.locator('#schedule-publish')).not.toBeVisible()
|
||||
|
||||
// save draft then try to schedule publish
|
||||
await saveDocAndAssert(page)
|
||||
await page.locator('#action-save-popup').click()
|
||||
await page.locator('#schedule-publish').click()
|
||||
|
||||
// drawer should open
|
||||
await expect(page.locator('.schedule-publish__drawer-header')).toBeVisible()
|
||||
// nothing in scheduled
|
||||
await expect(page.locator('.drawer__content')).toContainText('No upcoming events scheduled.')
|
||||
|
||||
// set date and time
|
||||
await page.locator('.date-time-picker input').fill('Feb 21, 2050 12:00 AM')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// save the scheduled publish
|
||||
await page.locator('#scheduled-publish-save').click()
|
||||
|
||||
// delete the scheduled event after it was made
|
||||
await page.locator('.cell-delete').locator('.btn').click()
|
||||
|
||||
// see toast deleted successfully
|
||||
await expect(
|
||||
page.locator('.payload-toast-item:has-text("Deleted successfully.")'),
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Collections - publish specific locale', () => {
|
||||
beforeAll(() => {
|
||||
url = new AdminUrlUtil(serverURL, localizedCollectionSlug)
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@payload-config": ["./test/versions/config.ts"],
|
||||
"@payload-config": ["./test/plugin-search/config.ts"],
|
||||
"@payloadcms/live-preview": ["./packages/live-preview/src"],
|
||||
"@payloadcms/live-preview-react": ["./packages/live-preview-react/src/index.ts"],
|
||||
"@payloadcms/live-preview-vue": ["./packages/live-preview-vue/src/index.ts"],
|
||||
|
||||
Reference in New Issue
Block a user