Add AnyClip integration tools and extracted source code

- Add authentication scripts with SubtleCrypto password encryption
- Add sourcemap extraction pipeline (update-urls, download-sourcemaps, extract-sources)
- Add Playwright API interception script for monetization endpoints
- Document two-step auth flow with JWT tokens and dual cookies
- Move extracted source from root to anyclip/ directory
- Add project configuration (.env.example, .gitignore, CLAUDE.md)
This commit is contained in:
2026-01-21 10:36:51 +08:00
parent d4fe4800e6
commit e32d475aa9
3463 changed files with 184648 additions and 64341 deletions

2
.env.example Normal file
View File

@@ -0,0 +1,2 @@
ANYCLIP_EMAIL=your-email@example.com
ANYCLIP_PASSWORD=your-password

41
.gitignore vendored Normal file
View File

@@ -0,0 +1,41 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
captured-apis.json
session.json
# Raw sourcemaps (large binary files)
sourcemaps/

151
CLAUDE.md Normal file
View File

@@ -0,0 +1,151 @@
---
description: AnyClip integration tools - use Bun, understand the scripts
globs: "*.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json"
alwaysApply: true
---
# Project: AnyClip Integration
Tools for integrating with AnyClip's Video Manager API and extracting source code from sourcemaps.
## Key Scripts
| Script | Purpose |
|--------|---------|
| `scripts/auth.ts` | Authenticate with AnyClip, returns session cookies |
| `scripts/crypto-subtle.ts` | Password encryption (AES-256-GCM, matches AnyClip's client) |
| `scripts/update-urls.ts` | Fetch JS URLs from build manifest → urls.txt |
| `scripts/download-sourcemaps.ts` | Download .map files for URLs in urls.txt |
| `scripts/extract-sources.ts` | Extract source from sourcemaps → anyclip/ |
## Authentication Flow
1. Password encrypted client-side with AES-256-GCM (salt: `$2b$04$wwky7rvtr6BFNaCqntwyie`)
2. POST to `videomanager-api.anyclip.com/public/auth/login` → returns `anyclip_2020` cookie + JWT
3. POST to `videomanager.anyclip.com/api/auth/login` → returns `session` cookie
4. Both cookies required for authenticated requests
## Source Extraction Workflow
```bash
bun scripts/update-urls.ts # Scrape build manifest for JS URLs
bun scripts/download-sourcemaps.ts # Download .map files
bun scripts/extract-sources.ts # Extract to anyclip/
```
## Directory Structure
- `anyclip/` - Extracted source (committed)
- `sourcemaps/` - Raw .map files (gitignored)
- `urls.txt` - JS URLs to download
- `session.json` - Auth session (gitignored)
---
# Bun Usage
Default to using Bun instead of Node.js.
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
- Use `bun test` instead of `jest` or `vitest`
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
- Use `bunx <package> <command>` instead of `npx <package> <command>`
- Bun automatically loads .env, so don't use dotenv.
## APIs
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
- `Bun.redis` for Redis. Don't use `ioredis`.
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
- `WebSocket` is built-in. Don't use `ws`.
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
- Bun.$`ls` instead of execa.
## Testing
Use `bun test` to run tests.
```ts#index.test.ts
import { test, expect } from "bun:test";
test("hello world", () => {
expect(1).toBe(1);
});
```
## Frontend
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
Server:
```ts#index.ts
import index from "./index.html"
Bun.serve({
routes: {
"/": index,
"/api/users/:id": {
GET: (req) => {
return new Response(JSON.stringify({ id: req.params.id }));
},
},
},
// optional websocket support
websocket: {
open: (ws) => {
ws.send("Hello, world!");
},
message: (ws, message) => {
ws.send(message);
},
close: (ws) => {
// handle close
}
},
development: {
hmr: true,
console: true,
}
})
```
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
```html#index.html
<html>
<body>
<h1>Hello, world!</h1>
<script type="module" src="./frontend.tsx"></script>
</body>
</html>
```
With the following `frontend.tsx`:
```tsx#frontend.tsx
import React from "react";
import { createRoot } from "react-dom/client";
// import .css files directly and it works
import './index.css';
const root = createRoot(document.body);
export default function Frontend() {
return <h1>Hello, world!</h1>;
}
root.render(<Frontend />);
```
Then, run index.ts
```sh
bun --hot ./index.ts
```
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.

113
README.md
View File

@@ -1,55 +1,78 @@
# AnyClip Video Manager - Extracted Source # AnyClip Integration
Source code extracted from sourcemaps of `videomanager.anyclip.com`. Tools for integrating with AnyClip's Video Manager API.
## Overview ## Setup
Next.js application for video content management, analytics, and publishing. ```bash
cp .env.example .env
## Structure # Edit .env with your AnyClip credentials
bun install
```
├── src/
│ ├── modules/ # Feature modules (business logic)
│ ├── pages/ # Next.js page components
│ ├── client/ # Client-side utilities
│ ├── shared/ # Shared libraries
│ └── assets/
├── pages/ # Root Next.js pages (_app.tsx, _error.tsx)
├── client/ # Next.js client runtime
├── vendor/ # Bundled node_modules
└── sourcemaps/ # Original .map files
``` ```
## Modules (`src/modules/`) ## Project Structure
| Module | Description | ```
|--------|-------------| ├── anyclip/ # Extracted source code (from sourcemaps)
| `analytics/` | Dashboards - monetization, video performance, custom reports | ├── docs/ # Documentation
| `editorial/` | Video editing - tagging, search, bulk actions, video details | ├── scripts/ # CLI tools
| `publishing/` | Content publishing and destination management | ├── sourcemaps/ # Raw .map files (gitignored)
| `marketplace/` | Marketplace accounts and dashboard | └── urls.txt # JS file URLs to download
| `xRay/` | X-Ray - campaigns, creatives, line items | ```
| `hubs/` | Content hubs management |
| `users/` | User management |
| `invitations/` | User invitation system |
| `forms/` | Form builder/management |
| `uploaderNew/` | Video upload functionality |
| `userRulesSettings/` | User rules and settings |
| `layout/` | App layout and Redux state |
| `common/` | Shared components - forms, tables, lists, tag selectors |
## Pages (`src/pages/`) ## Scripts
- `/analytics` - Analytics dashboard ### Authentication
- `/studio` - Studio interface
- `/personal-settings` - User settings
- `/hubs`, `/users`, `/invitations`, `/forms` - Management pages
- `/x-ray/*` - Campaign, creative, and line item analytics
## Tech Stack ```bash
# Login and save session to session.json
bun scripts/auth.ts
```
- Next.js, React, TypeScript Programmatic usage:
- Redux (state management)
- Material-UI (components) ```typescript
- Victory/D3 (charts) import { login, getAuthHeaders } from './scripts/auth';
const session = await login(email, password);
const headers = getAuthHeaders(session);
// session.cookies contains both required cookies
```
See [docs/auth.md](docs/auth.md) for details on the two-step auth flow.
### Source Extraction
Extract AnyClip's frontend source from public sourcemaps:
```bash
# 1. Update urls.txt from build manifest
bun scripts/update-urls.ts
# 2. Download sourcemaps for all URLs
bun scripts/download-sourcemaps.ts
# 3. Extract source files to anyclip/
bun scripts/extract-sources.ts
```
Or run all three:
```bash
bun scripts/update-urls.ts && bun scripts/download-sourcemaps.ts && bun scripts/extract-sources.ts
```
### Script Options
**extract-sources.ts:**
```bash
bun scripts/extract-sources.ts [options]
--output, -o <dir> Output directory (default: anyclip)
--input, -i <dir> Sourcemaps directory (default: sourcemaps)
--verbose, -v Verbose output
--no-clean Don't delete output directory first
```
## Documentation
- [docs/auth.md](docs/auth.md) - Authentication system (two-step flow, cookies, encryption)

View File

@@ -0,0 +1,35 @@
import type { DomainLocale } from '../server/config'
import type { normalizeLocalePath as NormalizeFn } from './normalize-locale-path'
import type { detectDomainLocale as DetectFn } from './detect-domain-locale'
import { normalizePathTrailingSlash } from './normalize-trailing-slash'
const basePath = (process.env.__NEXT_ROUTER_BASEPATH as string) || ''
export function getDomainLocale(
path: string,
locale?: string | false,
locales?: readonly string[],
domainLocales?: readonly DomainLocale[]
) {
if (process.env.__NEXT_I18N_SUPPORT) {
const normalizeLocalePath: typeof NormalizeFn = (
require('./normalize-locale-path') as typeof import('./normalize-locale-path')
).normalizeLocalePath
const detectDomainLocale: typeof DetectFn = (
require('./detect-domain-locale') as typeof import('./detect-domain-locale')
).detectDomainLocale
const target = locale || normalizeLocalePath(path, locales).detectedLocale
const domain = detectDomainLocale(domainLocales, undefined, target)
if (domain) {
const proto = `http${domain.http ? '' : 's'}://`
const finalLocale = target === domain.defaultLocale ? '' : `/${target}`
return `${proto}${domain.domain}${normalizePathTrailingSlash(
`${basePath}${finalLocale}${path}`
)}`
}
return false
} else {
return false
}
}

View File

@@ -0,0 +1,421 @@
'use client'
import React, {
useRef,
useEffect,
useCallback,
useContext,
useMemo,
useState,
forwardRef,
use,
} from 'react'
import ReactDOM from 'react-dom'
import Head from '../shared/lib/head'
import { getImgProps } from '../shared/lib/get-img-props'
import type {
ImageProps,
ImgProps,
OnLoad,
OnLoadingComplete,
PlaceholderValue,
} from '../shared/lib/get-img-props'
import type {
ImageConfigComplete,
ImageLoaderProps,
} from '../shared/lib/image-config'
import { imageConfigDefault } from '../shared/lib/image-config'
import { ImageConfigContext } from '../shared/lib/image-config-context.shared-runtime'
import { warnOnce } from '../shared/lib/utils/warn-once'
import { RouterContext } from '../shared/lib/router-context.shared-runtime'
// This is replaced by webpack alias
import defaultLoader from 'next/dist/shared/lib/image-loader'
import { useMergedRef } from './use-merged-ref'
// This is replaced by webpack define plugin
const configEnv = process.env.__NEXT_IMAGE_OPTS as any as ImageConfigComplete
if (typeof window === 'undefined') {
;(globalThis as any).__NEXT_IMAGE_IMPORTED = true
}
export type { ImageLoaderProps }
export type ImageLoader = (p: ImageLoaderProps) => string
type ImgElementWithDataProp = HTMLImageElement & {
'data-loaded-src': string | undefined
}
type ImageElementProps = ImgProps & {
unoptimized: boolean
placeholder: PlaceholderValue
onLoadRef: React.MutableRefObject<OnLoad | undefined>
onLoadingCompleteRef: React.MutableRefObject<OnLoadingComplete | undefined>
setBlurComplete: (b: boolean) => void
setShowAltText: (b: boolean) => void
sizesInput: string | undefined
}
// See https://stackoverflow.com/q/39777833/266535 for why we use this ref
// handler instead of the img's onLoad attribute.
function handleLoading(
img: ImgElementWithDataProp,
placeholder: PlaceholderValue,
onLoadRef: React.MutableRefObject<OnLoad | undefined>,
onLoadingCompleteRef: React.MutableRefObject<OnLoadingComplete | undefined>,
setBlurComplete: (b: boolean) => void,
unoptimized: boolean,
sizesInput: string | undefined
) {
const src = img?.src
if (!img || img['data-loaded-src'] === src) {
return
}
img['data-loaded-src'] = src
const p = 'decode' in img ? img.decode() : Promise.resolve()
p.catch(() => {}).then(() => {
if (!img.parentElement || !img.isConnected) {
// Exit early in case of race condition:
// - onload() is called
// - decode() is called but incomplete
// - unmount is called
// - decode() completes
return
}
if (placeholder !== 'empty') {
setBlurComplete(true)
}
if (onLoadRef?.current) {
// Since we don't have the SyntheticEvent here,
// we must create one with the same shape.
// See https://reactjs.org/docs/events.html
const event = new Event('load')
Object.defineProperty(event, 'target', { writable: false, value: img })
let prevented = false
let stopped = false
onLoadRef.current({
...event,
nativeEvent: event,
currentTarget: img,
target: img,
isDefaultPrevented: () => prevented,
isPropagationStopped: () => stopped,
persist: () => {},
preventDefault: () => {
prevented = true
event.preventDefault()
},
stopPropagation: () => {
stopped = true
event.stopPropagation()
},
})
}
if (onLoadingCompleteRef?.current) {
onLoadingCompleteRef.current(img)
}
if (process.env.NODE_ENV !== 'production') {
const origSrc = new URL(src, 'http://n').searchParams.get('url') || src
if (img.getAttribute('data-nimg') === 'fill') {
if (!unoptimized && (!sizesInput || sizesInput === '100vw')) {
let widthViewportRatio =
img.getBoundingClientRect().width / window.innerWidth
if (widthViewportRatio < 0.6) {
if (sizesInput === '100vw') {
warnOnce(
`Image with src "${origSrc}" has "fill" prop and "sizes" prop of "100vw", but image is not rendered at full viewport width. Please adjust "sizes" to improve page performance. Read more: https://nextjs.org/docs/api-reference/next/image#sizes`
)
} else {
warnOnce(
`Image with src "${origSrc}" has "fill" but is missing "sizes" prop. Please add it to improve page performance. Read more: https://nextjs.org/docs/api-reference/next/image#sizes`
)
}
}
}
if (img.parentElement) {
const { position } = window.getComputedStyle(img.parentElement)
const valid = ['absolute', 'fixed', 'relative']
if (!valid.includes(position)) {
warnOnce(
`Image with src "${origSrc}" has "fill" and parent element with invalid "position". Provided "${position}" should be one of ${valid
.map(String)
.join(',')}.`
)
}
}
if (img.height === 0) {
warnOnce(
`Image with src "${origSrc}" has "fill" and a height value of 0. This is likely because the parent element of the image has not been styled to have a set height.`
)
}
}
const heightModified =
img.height.toString() !== img.getAttribute('height')
const widthModified = img.width.toString() !== img.getAttribute('width')
if (
(heightModified && !widthModified) ||
(!heightModified && widthModified)
) {
warnOnce(
`Image with src "${origSrc}" has either width or height modified, but not the other. If you use CSS to change the size of your image, also include the styles 'width: "auto"' or 'height: "auto"' to maintain the aspect ratio.`
)
}
}
})
}
function getDynamicProps(
fetchPriority?: string
): Record<string, string | undefined> {
if (Boolean(use)) {
// In React 19.0.0 or newer, we must use camelCase
// prop to avoid "Warning: Invalid DOM property".
// See https://github.com/facebook/react/pull/25927
return { fetchPriority }
}
// In React 18.2.0 or older, we must use lowercase prop
// to avoid "Warning: Invalid DOM property".
return { fetchpriority: fetchPriority }
}
const ImageElement = forwardRef<HTMLImageElement | null, ImageElementProps>(
(
{
src,
srcSet,
sizes,
height,
width,
decoding,
className,
style,
fetchPriority,
placeholder,
loading,
unoptimized,
fill,
onLoadRef,
onLoadingCompleteRef,
setBlurComplete,
setShowAltText,
sizesInput,
onLoad,
onError,
...rest
},
forwardedRef
) => {
const ownRef = useCallback(
(img: ImgElementWithDataProp | null) => {
if (!img) {
return
}
if (onError) {
// If the image has an error before react hydrates, then the error is lost.
// The workaround is to wait until the image is mounted which is after hydration,
// then we set the src again to trigger the error handler (if there was an error).
// eslint-disable-next-line no-self-assign
img.src = img.src
}
if (process.env.NODE_ENV !== 'production') {
if (!src) {
console.error(`Image is missing required "src" property:`, img)
}
if (img.getAttribute('alt') === null) {
console.error(
`Image is missing required "alt" property. Please add Alternative Text to describe the image for screen readers and search engines.`
)
}
}
if (img.complete) {
handleLoading(
img,
placeholder,
onLoadRef,
onLoadingCompleteRef,
setBlurComplete,
unoptimized,
sizesInput
)
}
},
[
src,
placeholder,
onLoadRef,
onLoadingCompleteRef,
setBlurComplete,
onError,
unoptimized,
sizesInput,
]
)
const ref = useMergedRef(forwardedRef, ownRef)
return (
<img
{...rest}
{...getDynamicProps(fetchPriority)}
// It's intended to keep `loading` before `src` because React updates
// props in order which causes Safari/Firefox to not lazy load properly.
// See https://github.com/facebook/react/issues/25883
loading={loading}
width={width}
height={height}
decoding={decoding}
data-nimg={fill ? 'fill' : '1'}
className={className}
style={style}
// It's intended to keep `src` the last attribute because React updates
// attributes in order. If we keep `src` the first one, Safari will
// immediately start to fetch `src`, before `sizes` and `srcSet` are even
// updated by React. That causes multiple unnecessary requests if `srcSet`
// and `sizes` are defined.
// This bug cannot be reproduced in Chrome or Firefox.
sizes={sizes}
srcSet={srcSet}
src={src}
ref={ref}
onLoad={(event) => {
const img = event.currentTarget as ImgElementWithDataProp
handleLoading(
img,
placeholder,
onLoadRef,
onLoadingCompleteRef,
setBlurComplete,
unoptimized,
sizesInput
)
}}
onError={(event) => {
// if the real image fails to load, this will ensure "alt" is visible
setShowAltText(true)
if (placeholder !== 'empty') {
// If the real image fails to load, this will still remove the placeholder.
setBlurComplete(true)
}
if (onError) {
onError(event)
}
}}
/>
)
}
)
function ImagePreload({
isAppRouter,
imgAttributes,
}: {
isAppRouter: boolean
imgAttributes: ImgProps
}) {
const opts: ReactDOM.PreloadOptions = {
as: 'image',
imageSrcSet: imgAttributes.srcSet,
imageSizes: imgAttributes.sizes,
crossOrigin: imgAttributes.crossOrigin,
referrerPolicy: imgAttributes.referrerPolicy,
...getDynamicProps(imgAttributes.fetchPriority),
}
if (isAppRouter && ReactDOM.preload) {
ReactDOM.preload(imgAttributes.src, opts)
return null
}
return (
<Head>
<link
key={
'__nimg-' +
imgAttributes.src +
imgAttributes.srcSet +
imgAttributes.sizes
}
rel="preload"
// Note how we omit the `href` attribute, as it would only be relevant
// for browsers that do not support `imagesrcset`, and in those cases
// it would cause the incorrect image to be preloaded.
//
// https://html.spec.whatwg.org/multipage/semantics.html#attr-link-imagesrcset
href={imgAttributes.srcSet ? undefined : imgAttributes.src}
{...opts}
/>
</Head>
)
}
/**
* The `Image` component is used to optimize images.
*
* Read more: [Next.js docs: `Image`](https://nextjs.org/docs/app/api-reference/components/image)
*/
export const Image = forwardRef<HTMLImageElement | null, ImageProps>(
(props, forwardedRef) => {
const pagesRouter = useContext(RouterContext)
// We're in the app directory if there is no pages router.
const isAppRouter = !pagesRouter
const configContext = useContext(ImageConfigContext)
const config = useMemo(() => {
const c = configEnv || configContext || imageConfigDefault
const allSizes = [...c.deviceSizes, ...c.imageSizes].sort((a, b) => a - b)
const deviceSizes = c.deviceSizes.sort((a, b) => a - b)
const qualities = c.qualities?.sort((a, b) => a - b)
return { ...c, allSizes, deviceSizes, qualities }
}, [configContext])
const { onLoad, onLoadingComplete } = props
const onLoadRef = useRef(onLoad)
useEffect(() => {
onLoadRef.current = onLoad
}, [onLoad])
const onLoadingCompleteRef = useRef(onLoadingComplete)
useEffect(() => {
onLoadingCompleteRef.current = onLoadingComplete
}, [onLoadingComplete])
const [blurComplete, setBlurComplete] = useState(false)
const [showAltText, setShowAltText] = useState(false)
const { props: imgAttributes, meta: imgMeta } = getImgProps(props, {
defaultLoader,
imgConf: config,
blurComplete,
showAltText,
})
return (
<>
{
<ImageElement
{...imgAttributes}
unoptimized={imgMeta.unoptimized}
placeholder={imgMeta.placeholder}
fill={imgMeta.fill}
onLoadRef={onLoadRef}
onLoadingCompleteRef={onLoadingCompleteRef}
setBlurComplete={setBlurComplete}
setShowAltText={setShowAltText}
sizesInput={props.sizes}
ref={forwardedRef}
/>
}
{imgMeta.priority ? (
<ImagePreload
isAppRouter={isAppRouter}
imgAttributes={imgAttributes}
/>
) : null}
</>
)
}
)

717
anyclip/client/link.tsx Normal file
View File

@@ -0,0 +1,717 @@
'use client'
import type {
NextRouter,
PrefetchOptions as RouterPrefetchOptions,
} from '../shared/lib/router/router'
import React, { createContext, useContext } from 'react'
import type { UrlObject } from 'url'
import { resolveHref } from './resolve-href'
import { isLocalURL } from '../shared/lib/router/utils/is-local-url'
import { formatUrl } from '../shared/lib/router/utils/format-url'
import { isAbsoluteUrl } from '../shared/lib/utils'
import { addLocale } from './add-locale'
import { RouterContext } from '../shared/lib/router-context.shared-runtime'
import type { AppRouterInstance } from '../shared/lib/app-router-context.shared-runtime'
import { useIntersection } from './use-intersection'
import { getDomainLocale } from './get-domain-locale'
import { addBasePath } from './add-base-path'
import { useMergedRef } from './use-merged-ref'
import { errorOnce } from '../shared/lib/utils/error-once'
type Url = string | UrlObject
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K
}[keyof T]
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never
}[keyof T]
type OnNavigateEventHandler = (event: { preventDefault: () => void }) => void
type InternalLinkProps = {
/**
* The path or URL to navigate to. It can also be an object.
*
* @example https://nextjs.org/docs/api-reference/next/link#with-url-object
*/
href: Url
/**
* Optional decorator for the path that will be shown in the browser URL bar. Before Next.js 9.5.3 this was used for dynamic routes, check our [previous docs](https://github.com/vercel/next.js/blob/v9.5.2/docs/api-reference/next/link.md#dynamic-routes) to see how it worked. Note: when this path differs from the one provided in `href` the previous `href`/`as` behavior is used as shown in the [previous docs](https://github.com/vercel/next.js/blob/v9.5.2/docs/api-reference/next/link.md#dynamic-routes).
*/
as?: Url
/**
* Replace the current `history` state instead of adding a new url into the stack.
*
* @defaultValue `false`
*/
replace?: boolean
/**
* Whether to override the default scroll behavior
*
* @example https://nextjs.org/docs/api-reference/next/link#disable-scrolling-to-the-top-of-the-page
*
* @defaultValue `true`
*/
scroll?: boolean
/**
* Update the path of the current page without rerunning [`getStaticProps`](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-props), [`getServerSideProps`](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props) or [`getInitialProps`](/docs/pages/api-reference/functions/get-initial-props).
*
* @defaultValue `false`
*/
shallow?: boolean
/**
* Forces `Link` to send the `href` property to its child.
*
* @defaultValue `false`
*/
passHref?: boolean
/**
* Prefetch the page in the background.
* Any `<Link />` that is in the viewport (initially or through scroll) will be prefetched.
* Prefetch can be disabled by passing `prefetch={false}`. Prefetching is only enabled in production.
*
* In App Router:
* - "auto", null, undefined (default): For statically generated pages, this will prefetch the full React Server Component data. For dynamic pages, this will prefetch up to the nearest route segment with a [`loading.js`](https://nextjs.org/docs/app/api-reference/file-conventions/loading) file. If there is no loading file, it will not fetch the full tree to avoid fetching too much data.
* - `true`: This will prefetch the full React Server Component data for all route segments, regardless of whether they contain a segment with `loading.js`.
* - `false`: This will not prefetch any data, even on hover.
*
* In Pages Router:
* - `true` (default): The full route & its data will be prefetched.
* - `false`: Prefetching will not happen when entering the viewport, but will still happen on hover.
* @defaultValue `true` (pages router) or `null` (app router)
*/
prefetch?: boolean | 'auto' | null | 'unstable_forceStale'
/**
* The active locale is automatically prepended. `locale` allows for providing a different locale.
* When `false` `href` has to include the locale as the default behavior is disabled.
* Note: This is only available in the Pages Router.
*/
locale?: string | false
/**
* Enable legacy link behavior.
* @deprecated This will be removed in v16
* @defaultValue `false`
* @see https://github.com/vercel/next.js/commit/489e65ed98544e69b0afd7e0cfc3f9f6c2b803b7
*/
legacyBehavior?: boolean
/**
* Optional event handler for when the mouse pointer is moved onto Link
*/
onMouseEnter?: React.MouseEventHandler<HTMLAnchorElement>
/**
* Optional event handler for when Link is touched.
*/
onTouchStart?: React.TouchEventHandler<HTMLAnchorElement>
/**
* Optional event handler for when Link is clicked.
*/
onClick?: React.MouseEventHandler<HTMLAnchorElement>
/**
* Optional event handler for when the `<Link>` is navigated.
*/
onNavigate?: OnNavigateEventHandler
}
// TODO-APP: Include the full set of Anchor props
// adding this to the publicly exported type currently breaks existing apps
// `RouteInferType` is a stub here to avoid breaking `typedRoutes` when the type
// isn't generated yet. It will be replaced when type generation runs.
// WARNING: This should be an interface to prevent TypeScript from inlining it
// in declarations of libraries dependending on Next.js.
// Not trivial to reproduce so only convert to an interface when needed.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export interface LinkProps<RouteInferType = any> extends InternalLinkProps {}
type LinkPropsRequired = RequiredKeys<LinkProps>
type LinkPropsOptional = OptionalKeys<InternalLinkProps>
const prefetched = new Set<string>()
type PrefetchOptions = RouterPrefetchOptions & {
/**
* bypassPrefetchedCheck will bypass the check to see if the `href` has
* already been fetched.
*/
bypassPrefetchedCheck?: boolean
}
function prefetch(
router: NextRouter,
href: string,
as: string,
options: PrefetchOptions
): void {
if (typeof window === 'undefined') {
return
}
if (!isLocalURL(href)) {
return
}
// We should only dedupe requests when experimental.optimisticClientCache is
// disabled.
if (!options.bypassPrefetchedCheck) {
const locale =
// Let the link's locale prop override the default router locale.
typeof options.locale !== 'undefined'
? options.locale
: // Otherwise fallback to the router's locale.
'locale' in router
? router.locale
: undefined
const prefetchedKey = href + '%' + as + '%' + locale
// If we've already fetched the key, then don't prefetch it again!
if (prefetched.has(prefetchedKey)) {
return
}
// Mark this URL as prefetched.
prefetched.add(prefetchedKey)
}
// Prefetch the JSON page if asked (only in the client)
// We need to handle a prefetch error here since we may be
// loading with priority which can reject but we don't
// want to force navigation since this is only a prefetch
router.prefetch(href, as, options).catch((err) => {
if (process.env.NODE_ENV !== 'production') {
// rethrow to show invalid URL errors
throw err
}
})
}
function isModifiedEvent(event: React.MouseEvent): boolean {
const eventTarget = event.currentTarget as HTMLAnchorElement | SVGAElement
const target = eventTarget.getAttribute('target')
return (
(target && target !== '_self') ||
event.metaKey ||
event.ctrlKey ||
event.shiftKey ||
event.altKey || // triggers resource download
(event.nativeEvent && event.nativeEvent.which === 2)
)
}
function linkClicked(
e: React.MouseEvent,
router: NextRouter | AppRouterInstance,
href: string,
as: string,
replace?: boolean,
shallow?: boolean,
scroll?: boolean,
locale?: string | false,
onNavigate?: OnNavigateEventHandler
): void {
const { nodeName } = e.currentTarget
// anchors inside an svg have a lowercase nodeName
const isAnchorNodeName = nodeName.toUpperCase() === 'A'
if (
(isAnchorNodeName && isModifiedEvent(e)) ||
e.currentTarget.hasAttribute('download')
) {
// ignore click for browsers default behavior
return
}
if (!isLocalURL(href)) {
if (replace) {
// browser default behavior does not replace the history state
// so we need to do it manually
e.preventDefault()
location.replace(href)
}
// ignore click for browsers default behavior
return
}
e.preventDefault()
const navigate = () => {
if (onNavigate) {
let isDefaultPrevented = false
onNavigate({
preventDefault: () => {
isDefaultPrevented = true
},
})
if (isDefaultPrevented) {
return
}
}
// If the router is an NextRouter instance it will have `beforePopState`
const routerScroll = scroll ?? true
if ('beforePopState' in router) {
router[replace ? 'replace' : 'push'](href, as, {
shallow,
locale,
scroll: routerScroll,
})
} else {
router[replace ? 'replace' : 'push'](as || href, {
scroll: routerScroll,
})
}
}
navigate()
}
type LinkPropsReal = React.PropsWithChildren<
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof LinkProps> &
LinkProps
>
function formatStringOrUrl(urlObjOrString: UrlObject | string): string {
if (typeof urlObjOrString === 'string') {
return urlObjOrString
}
return formatUrl(urlObjOrString)
}
/**
* A React component that extends the HTML `<a>` element to provide [prefetching](https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#2-prefetching)
* and client-side navigation between routes.
*
* It is the primary way to navigate between routes in Next.js.
*
* Read more: [Next.js docs: `<Link>`](https://nextjs.org/docs/app/api-reference/components/link)
*/
const Link = React.forwardRef<HTMLAnchorElement, LinkPropsReal>(
function LinkComponent(props, forwardedRef) {
let children: React.ReactNode
const {
href: hrefProp,
as: asProp,
children: childrenProp,
prefetch: prefetchProp = null,
passHref,
replace,
shallow,
scroll,
locale,
onClick,
onNavigate,
onMouseEnter: onMouseEnterProp,
onTouchStart: onTouchStartProp,
legacyBehavior = false,
...restProps
} = props
children = childrenProp
if (
legacyBehavior &&
(typeof children === 'string' || typeof children === 'number')
) {
children = <a>{children}</a>
}
const router = React.useContext(RouterContext)
const prefetchEnabled = prefetchProp !== false
if (process.env.NODE_ENV !== 'production') {
function createPropError(args: {
key: string
expected: string
actual: string
}) {
return new Error(
`Failed prop type: The prop \`${args.key}\` expects a ${args.expected} in \`<Link>\`, but got \`${args.actual}\` instead.` +
(typeof window !== 'undefined'
? // TODO: Remove this addendum if Owner Stacks are available
"\nOpen your browser's console to view the Component stack trace."
: '')
)
}
// TypeScript trick for type-guarding:
const requiredPropsGuard: Record<LinkPropsRequired, true> = {
href: true,
} as const
const requiredProps: LinkPropsRequired[] = Object.keys(
requiredPropsGuard
) as LinkPropsRequired[]
requiredProps.forEach((key: LinkPropsRequired) => {
if (key === 'href') {
if (
props[key] == null ||
(typeof props[key] !== 'string' && typeof props[key] !== 'object')
) {
throw createPropError({
key,
expected: '`string` or `object`',
actual: props[key] === null ? 'null' : typeof props[key],
})
}
} else {
// TypeScript trick for type-guarding:
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _: never = key
}
})
// TypeScript trick for type-guarding:
const optionalPropsGuard: Record<LinkPropsOptional, true> = {
as: true,
replace: true,
scroll: true,
shallow: true,
passHref: true,
prefetch: true,
locale: true,
onClick: true,
onMouseEnter: true,
onTouchStart: true,
legacyBehavior: true,
onNavigate: true,
} as const
const optionalProps: LinkPropsOptional[] = Object.keys(
optionalPropsGuard
) as LinkPropsOptional[]
optionalProps.forEach((key: LinkPropsOptional) => {
const valType = typeof props[key]
if (key === 'as') {
if (props[key] && valType !== 'string' && valType !== 'object') {
throw createPropError({
key,
expected: '`string` or `object`',
actual: valType,
})
}
} else if (key === 'locale') {
if (props[key] && valType !== 'string') {
throw createPropError({
key,
expected: '`string`',
actual: valType,
})
}
} else if (
key === 'onClick' ||
key === 'onMouseEnter' ||
key === 'onTouchStart' ||
key === 'onNavigate'
) {
if (props[key] && valType !== 'function') {
throw createPropError({
key,
expected: '`function`',
actual: valType,
})
}
} else if (
key === 'replace' ||
key === 'scroll' ||
key === 'shallow' ||
key === 'passHref' ||
key === 'legacyBehavior'
) {
if (props[key] != null && valType !== 'boolean') {
throw createPropError({
key,
expected: '`boolean`',
actual: valType,
})
}
} else if (key === 'prefetch') {
if (
props[key] != null &&
valType !== 'boolean' &&
props[key] !== 'auto'
) {
throw createPropError({
key,
expected: '`boolean | "auto"`',
actual: valType,
})
}
} else {
// TypeScript trick for type-guarding:
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _: never = key
}
})
}
const { href, as } = React.useMemo(() => {
if (!router) {
const resolvedHref = formatStringOrUrl(hrefProp)
return {
href: resolvedHref,
as: asProp ? formatStringOrUrl(asProp) : resolvedHref,
}
}
const [resolvedHref, resolvedAs] = resolveHref(router, hrefProp, true)
return {
href: resolvedHref,
as: asProp ? resolveHref(router, asProp) : resolvedAs || resolvedHref,
}
}, [router, hrefProp, asProp])
const previousHref = React.useRef<string>(href)
const previousAs = React.useRef<string>(as)
// This will return the first child, if multiple are provided it will throw an error
let child: any
if (legacyBehavior) {
if (process.env.NODE_ENV === 'development') {
if (onClick) {
console.warn(
`"onClick" was passed to <Link> with \`href\` of \`${hrefProp}\` but "legacyBehavior" was set. The legacy behavior requires onClick be set on the child of next/link`
)
}
if (onMouseEnterProp) {
console.warn(
`"onMouseEnter" was passed to <Link> with \`href\` of \`${hrefProp}\` but "legacyBehavior" was set. The legacy behavior requires onMouseEnter be set on the child of next/link`
)
}
try {
child = React.Children.only(children)
} catch (err) {
if (!children) {
throw new Error(
`No children were passed to <Link> with \`href\` of \`${hrefProp}\` but one child is required https://nextjs.org/docs/messages/link-no-children`
)
}
throw new Error(
`Multiple children were passed to <Link> with \`href\` of \`${hrefProp}\` but only one child is supported https://nextjs.org/docs/messages/link-multiple-children` +
(typeof window !== 'undefined'
? " \nOpen your browser's console to view the Component stack trace."
: '')
)
}
} else {
child = React.Children.only(children)
}
} else {
if (process.env.NODE_ENV === 'development') {
if ((children as any)?.type === 'a') {
throw new Error(
'Invalid <Link> with <a> child. Please remove <a> or use <Link legacyBehavior>.\nLearn more: https://nextjs.org/docs/messages/invalid-new-link-with-extra-anchor'
)
}
}
}
const childRef: any = legacyBehavior
? child && typeof child === 'object' && child.ref
: forwardedRef
const [setIntersectionRef, isVisible, resetVisible] = useIntersection({
rootMargin: '200px',
})
const setIntersectionWithResetRef = React.useCallback(
(el: Element | null) => {
// Before the link getting observed, check if visible state need to be reset
if (previousAs.current !== as || previousHref.current !== href) {
resetVisible()
previousAs.current = as
previousHref.current = href
}
setIntersectionRef(el)
},
[as, href, resetVisible, setIntersectionRef]
)
const setRef = useMergedRef(setIntersectionWithResetRef, childRef)
// Prefetch the URL if we haven't already and it's visible.
React.useEffect(() => {
// in dev, we only prefetch on hover to avoid wasting resources as the prefetch will trigger compiling the page.
if (process.env.NODE_ENV !== 'production') {
return
}
if (!router) {
return
}
// If we don't need to prefetch the URL, don't do prefetch.
if (!isVisible || !prefetchEnabled) {
return
}
// Prefetch the URL.
prefetch(router, href, as, { locale })
}, [as, href, isVisible, locale, prefetchEnabled, router?.locale, router])
const childProps: {
onTouchStart?: React.TouchEventHandler<HTMLAnchorElement>
onMouseEnter: React.MouseEventHandler<HTMLAnchorElement>
onClick: React.MouseEventHandler<HTMLAnchorElement>
href?: string
ref?: any
} = {
ref: setRef,
onClick(e) {
if (process.env.NODE_ENV !== 'production') {
if (!e) {
throw new Error(
`Component rendered inside next/link has to pass click event to "onClick" prop.`
)
}
}
if (!legacyBehavior && typeof onClick === 'function') {
onClick(e)
}
if (
legacyBehavior &&
child.props &&
typeof child.props.onClick === 'function'
) {
child.props.onClick(e)
}
if (!router) {
return
}
if (e.defaultPrevented) {
return
}
linkClicked(
e,
router,
href,
as,
replace,
shallow,
scroll,
locale,
onNavigate
)
},
onMouseEnter(e) {
if (!legacyBehavior && typeof onMouseEnterProp === 'function') {
onMouseEnterProp(e)
}
if (
legacyBehavior &&
child.props &&
typeof child.props.onMouseEnter === 'function'
) {
child.props.onMouseEnter(e)
}
if (!router) {
return
}
prefetch(router, href, as, {
locale,
priority: true,
// @see {https://github.com/vercel/next.js/discussions/40268?sort=top#discussioncomment-3572642}
bypassPrefetchedCheck: true,
})
},
onTouchStart: process.env.__NEXT_LINK_NO_TOUCH_START
? undefined
: function onTouchStart(e) {
if (!legacyBehavior && typeof onTouchStartProp === 'function') {
onTouchStartProp(e)
}
if (
legacyBehavior &&
child.props &&
typeof child.props.onTouchStart === 'function'
) {
child.props.onTouchStart(e)
}
if (!router) {
return
}
prefetch(router, href, as, {
locale,
priority: true,
// @see {https://github.com/vercel/next.js/discussions/40268?sort=top#discussioncomment-3572642}
bypassPrefetchedCheck: true,
})
},
}
// If child is an <a> tag and doesn't have a href attribute, or if the 'passHref' property is
// defined, we specify the current 'href', so that repetition is not needed by the user.
// If the url is absolute, we can bypass the logic to prepend the domain and locale.
if (isAbsoluteUrl(as)) {
childProps.href = as
} else if (
!legacyBehavior ||
passHref ||
(child.type === 'a' && !('href' in child.props))
) {
const curLocale = typeof locale !== 'undefined' ? locale : router?.locale
// we only render domain locales if we are currently on a domain locale
// so that locale links are still visitable in development/preview envs
const localeDomain =
router?.isLocaleDomain &&
getDomainLocale(as, curLocale, router?.locales, router?.domainLocales)
childProps.href =
localeDomain ||
addBasePath(addLocale(as, curLocale, router?.defaultLocale))
}
if (legacyBehavior) {
if (process.env.NODE_ENV === 'development') {
errorOnce(
'`legacyBehavior` is deprecated and will be removed in a future ' +
'release. A codemod is available to upgrade your components:\n\n' +
'npx @next/codemod@latest new-link .\n\n' +
'Learn more: https://nextjs.org/docs/app/building-your-application/upgrading/codemods#remove-a-tags-from-link-components'
)
}
return React.cloneElement(child, childProps)
}
return (
<a {...restProps} {...childProps}>
{children}
</a>
)
}
)
const LinkStatusContext = createContext<{
pending: boolean
}>({
// We do not support link status in the Pages Router, so we always return false
pending: false,
})
export const useLinkStatus = () => {
// This behaviour is like React's useFormStatus. When the component is not under
// a <form> tag, it will get the default value, instead of throwing an error.
return useContext(LinkStatusContext)
}
export default Link

View File

@@ -0,0 +1,137 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import {
requestIdleCallback,
cancelIdleCallback,
} from './request-idle-callback'
type UseIntersectionObserverInit = Pick<
IntersectionObserverInit,
'rootMargin' | 'root'
>
type UseIntersection = { disabled?: boolean } & UseIntersectionObserverInit & {
rootRef?: React.RefObject<HTMLElement | null> | null
}
type ObserveCallback = (isVisible: boolean) => void
type Identifier = {
root: Element | Document | null
margin: string
}
type Observer = {
id: Identifier
observer: IntersectionObserver
elements: Map<Element, ObserveCallback>
}
const hasIntersectionObserver = typeof IntersectionObserver === 'function'
const observers = new Map<Identifier, Observer>()
const idList: Identifier[] = []
function createObserver(options: UseIntersectionObserverInit): Observer {
const id = {
root: options.root || null,
margin: options.rootMargin || '',
}
const existing = idList.find(
(obj) => obj.root === id.root && obj.margin === id.margin
)
let instance: Observer | undefined
if (existing) {
instance = observers.get(existing)
if (instance) {
return instance
}
}
const elements = new Map<Element, ObserveCallback>()
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const callback = elements.get(entry.target)
const isVisible = entry.isIntersecting || entry.intersectionRatio > 0
if (callback && isVisible) {
callback(isVisible)
}
})
}, options)
instance = {
id,
observer,
elements,
}
idList.push(id)
observers.set(id, instance)
return instance
}
function observe(
element: Element,
callback: ObserveCallback,
options: UseIntersectionObserverInit
): () => void {
const { id, observer, elements } = createObserver(options)
elements.set(element, callback)
observer.observe(element)
return function unobserve(): void {
elements.delete(element)
observer.unobserve(element)
// Destroy observer when there's nothing left to watch:
if (elements.size === 0) {
observer.disconnect()
observers.delete(id)
const index = idList.findIndex(
(obj) => obj.root === id.root && obj.margin === id.margin
)
if (index > -1) {
idList.splice(index, 1)
}
}
}
}
export function useIntersection<T extends Element>({
rootRef,
rootMargin,
disabled,
}: UseIntersection): [(element: T | null) => void, boolean, () => void] {
const isDisabled: boolean = disabled || !hasIntersectionObserver
const [visible, setVisible] = useState(false)
const elementRef = useRef<T | null>(null)
const setElement = useCallback((element: T | null) => {
elementRef.current = element
}, [])
useEffect(() => {
if (hasIntersectionObserver) {
if (isDisabled || visible) return
const element = elementRef.current
if (element && element.tagName) {
const unobserve = observe(
element,
(isVisible) => isVisible && setVisible(isVisible),
{ root: rootRef?.current, rootMargin }
)
return unobserve
}
} else {
if (!visible) {
const idleCallback = requestIdleCallback(() => setVisible(true))
return () => cancelIdleCallback(idleCallback)
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDisabled, rootMargin, rootRef, visible, elementRef.current])
const resetVisible = useCallback(() => {
setVisible(false)
}, [])
return [setElement, visible, resetVisible]
}

View File

@@ -0,0 +1,67 @@
import { useCallback, useRef, type Ref } from 'react'
// This is a compatibility hook to support React 18 and 19 refs.
// In 19, a cleanup function from refs may be returned.
// In 18, returning a cleanup function creates a warning.
// Since we take userspace refs, we don't know ahead of time if a cleanup function will be returned.
// This implements cleanup functions with the old behavior in 18.
// We know refs are always called alternating with `null` and then `T`.
// So a call with `null` means we need to call the previous cleanup functions.
export function useMergedRef<TElement>(
refA: Ref<TElement>,
refB: Ref<TElement>
): Ref<TElement> {
const cleanupA = useRef<(() => void) | null>(null)
const cleanupB = useRef<(() => void) | null>(null)
// NOTE: In theory, we could skip the wrapping if only one of the refs is non-null.
// (this happens often if the user doesn't pass a ref to Link/Form/Image)
// But this can cause us to leak a cleanup-ref into user code (e.g. via `<Link legacyBehavior>`),
// and the user might pass that ref into ref-merging library that doesn't support cleanup refs
// (because it hasn't been updated for React 19)
// which can then cause things to blow up, because a cleanup-returning ref gets called with `null`.
// So in practice, it's safer to be defensive and always wrap the ref, even on React 19.
return useCallback(
(current: TElement | null): void => {
if (current === null) {
const cleanupFnA = cleanupA.current
if (cleanupFnA) {
cleanupA.current = null
cleanupFnA()
}
const cleanupFnB = cleanupB.current
if (cleanupFnB) {
cleanupB.current = null
cleanupFnB()
}
} else {
if (refA) {
cleanupA.current = applyRef(refA, current)
}
if (refB) {
cleanupB.current = applyRef(refB, current)
}
}
},
[refA, refB]
)
}
function applyRef<TElement>(
refA: NonNullable<Ref<TElement>>,
current: TElement
) {
if (typeof refA === 'function') {
const cleanup = refA(current)
if (typeof cleanup === 'function') {
return cleanup
} else {
return () => refA(null)
}
} else {
refA.current = current
return () => {
refA.current = null
}
}
}

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/android-chrome-192x192-1621329404738.9a2c292c.png","height":192,"width":192,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAOVBMVEVnZ2fIyMj////Ozs7///+UlJTX19deXl7u7u5MaXEwMDD///8CAgLV1dWSkpKgoKBUVFSvr6+Li4vsoX4EAAAADHRSTlPy/DD2LbPu/LIA+S9SUaEGAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAP0lEQVR4nC3LUQrAIAwE0dVEE+smau9/2CL0b+AxGOqCpgO1p5Go8LSIhw1isU4hIDty3fD3p9lzk5gY6uXuH0i+AhIzA+vTAAAAAElFTkSuQmCC","blurWidth":8,"blurHeight":8};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/android-chrome-512x512-1621329404738.18838b10.png","height":512,"width":512,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAP1BMVEX///8wMDBlZWWZmZlnZ2deXl7X19fKysrv7+9MaXH///9hYWGPj4+QkJACAgLV1dWTk5Ofn59VVVWvr6+MjIwGk5XMAAAADnRSTlMv+eyx9v3v+rIALfm1spwOkf4AAAAJcEhZcwAACxMAAAsTAQCanBgAAAA/SURBVHicJcpbDoAgDAXRq/aJ2hZk/2sl6t8kZ+AwpQsOSHHEDlidmVscEM7+aBB0ZPU3bPJPkBrf7M2U7uYLUJgCVDLd5W0AAAAASUVORK5CYII=","blurWidth":8,"blurHeight":8};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/apple-touch-icon-1621329404738.718b739d.png","height":180,"width":180,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAQlBMVEVkZGTv7+9XV1fMzMxdXV3////X19f///9MaXGUlJQvLy/Jycnt7e1qamrIyMj///8CAgLW1taRkZGdnZ2wsLBQUFBGzLF2AAAAEHRSTlPrsv71+y/uLQCv+f6v9+ksuK1TXgAAAAlwSFlzAAALEwAACxMBAJqcGAAAAD9JREFUeJw1ykkSwCAIBdFvHICoEVDvf1UrC3dd9RpMIXYhRi7+qCIjeDJ7VVCSzVUViNt8/tEu0Z15tA9CfABaOgKVMNAZMgAAAABJRU5ErkJggg==","blurWidth":8,"blurHeight":8};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/favicon-16x16-1621329404738.428e8ba6.png","height":16,"width":16,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAQlBMVEVmZmb///+Ojo4vLy/V1dVeXl7////JyclMaXHs7Oz///+VlZX////q6uoCAgLV1dWSkpJcXFyhoaFTU1Ovr6+KiooSl+HfAAAADnRSTlPzLbT57/kv+wCzLrEssSJz7GwAAAAJcEhZcwAACxMAAAsTAQCanBgAAABBSURBVHicLctRDoAgDATRRSotiNsCev+rGhL/JnkZWKqKIxmKxCBRUON0z7wgw+ejBHR5zB33+1OTWCQarO+92wdWAwJ7fte5tAAAAABJRU5ErkJggg==","blurWidth":8,"blurHeight":8};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/favicon-32x32-1621329404738.a0439f9c.png","height":32,"width":32,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAAOVBMVEX////Ly8uPj4+ZmZnV1dVMaXH///9nZ2ddXV3t7e0vLy8uLi4CAgLV1dWQkJCgoKDHx8dUVFSwsLDm6qi1AAAADHRSTlMu97Sx8QAw8/2y+vpPqVL3AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAPklEQVR4nDXLQQ6AIAwF0VEKBf0t4v0PazRxN8nLUBmb71SaZZe8MbJHHCpYj3lfcmxFzjc+OlXAckkO//4ATbECJt5kAZwAAAAASUVORK5CYII=","blurWidth":8,"blurHeight":8};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/blue.355e1246.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAABlBMVEUzjMs8ks+XDtMVAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAEUlEQVR4nGNgYGBgZGRkZAAAABcABdzbLC4AAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/coffee.7bec6fe0.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAACVBMVEXV5OTDzsytsKlKK1TwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEUlEQVR4nGNgYGBgYmRgYAAAABcABNbHRhYAAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/desk.0278fd77.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAACVBMVEWUpKair7Cwvb2EUuI8AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEUlEQVR4nGNgYGBgYmRgYAAAABcABNbHRhYAAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/flower.0669d83d.png","height":80,"width":1536,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAACVBMVEW327O0yq2+rafkqDzXAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEUlEQVR4nGNgYGBkYmRgYAAAAB0ABRyOgNQAAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/green.356a9894.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAADFBMVEV5y69jwJ5au5JuxqgswQcvAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAEUlEQVR4nGNgYGBgZmRkYgIAACUACkFmRE0AAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/notebook.81224792.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAABlBMVEX0zWny3J+HstVbAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEUlEQVR4nGNgYGBgZGBgYAAAAA4AAnyhWpcAAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/pink.9180c2dd.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAACVBMVEX9pLT9rrv9wMa2bv0rAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAEUlEQVR4nGNgZGBiYmRkYAAAAC4ACOhUCY8AAAAASUVORK5CYII=","blurWidth":8,"blurHeight":1};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/textureblue.77c9842d.png","height":80,"width":1920,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAABCAMAAADU3h9xAAAABlBMVEViyspgycnqWTS1AAAACXBIWXMAAAsTAAALEwEAmpwYAAAADUlEQVR4nGNgYGQAAwAAEAACr3pF0AAAAABJRU5ErkJggg==","blurWidth":8,"blurHeight":1};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/logo.564beaa1.png","height":128,"width":507,"blurDataURL":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAACCAMAAABSSm3fAAAACVBMVEX///////////+OSuX+AAAAA3RSTlMlMVUZrOIcAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAF0lEQVR4nGNgZGJgZGRgYGBgZALRjAwAAIEADMddC4AAAAAASUVORK5CYII=","blurWidth":8,"blurHeight":2};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/instagram.f8cc4a31.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/mrss.245f1778.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/ms_stream.9514a9d7.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/sharepoint.44a77c14.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/teams.ab654a7b.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/tiktok.04e40fb5.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/zoom.53b9653f.svg","height":21,"width":21,"blurWidth":0,"blurHeight":0};

View File

@@ -0,0 +1 @@
export default {"src":"/_next/static/media/default-img-audio-thumbnail.0f14be28.jpg","height":672,"width":1196,"blurDataURL":"data:image/jpeg;base64,/9j/2wBDAAoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/v/2wBDAQoKCgoKCgsMDAsPEA4QDxYUExMUFiIYGhgaGCIzICUgICUgMy03LCksNy1RQDg4QFFeT0pPXnFlZXGPiI+7u/v/wgARCAAEAAgDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAf/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAACOg//EABoQAAICAwAAAAAAAAAAAAAAABETACECEjH/2gAIAQEAAT8AZktdDY8uf//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQIBAT8Af//EABQRAQAAAAAAAAAAAAAAAAAAAAD/2gAIAQMBAT8Af//Z","blurWidth":8,"blurHeight":4};

View File

@@ -0,0 +1,33 @@
import {
HTTP_ERROR_FALLBACK_ERROR_CODE,
type HTTPAccessFallbackError,
} from './http-access-fallback/http-access-fallback'
// TODO: Add `forbidden` docs
/**
* @experimental
* This function allows you to render the [forbidden.js file](https://nextjs.org/docs/app/api-reference/file-conventions/forbidden)
* within a route segment as well as inject a tag.
*
* `forbidden()` can be used in
* [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components),
* [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers), and
* [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
*
* Read more: [Next.js Docs: `forbidden`](https://nextjs.org/docs/app/api-reference/functions/forbidden)
*/
const DIGEST = `${HTTP_ERROR_FALLBACK_ERROR_CODE};403`
export function forbidden(): never {
if (!process.env.__NEXT_EXPERIMENTAL_AUTH_INTERRUPTS) {
throw new Error(
`\`forbidden()\` is experimental and only allowed to be enabled when \`experimental.authInterrupts\` is enabled.`
)
}
// eslint-disable-next-line no-throw-literal
const error = new Error(DIGEST) as HTTPAccessFallbackError
;(error as HTTPAccessFallbackError).digest = DIGEST
throw error
}

View File

@@ -0,0 +1,41 @@
/** @internal */
class ReadonlyURLSearchParamsError extends Error {
constructor() {
super(
'Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams'
)
}
}
class ReadonlyURLSearchParams extends URLSearchParams {
/** @deprecated Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams */
append() {
throw new ReadonlyURLSearchParamsError()
}
/** @deprecated Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams */
delete() {
throw new ReadonlyURLSearchParamsError()
}
/** @deprecated Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams */
set() {
throw new ReadonlyURLSearchParamsError()
}
/** @deprecated Method unavailable on `ReadonlyURLSearchParams`. Read more: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams */
sort() {
throw new ReadonlyURLSearchParamsError()
}
}
export function unstable_isUnrecognizedActionError(): boolean {
throw new Error(
'`unstable_isUnrecognizedActionError` can only be used on the client.'
)
}
export { redirect, permanentRedirect } from './redirect'
export { RedirectType } from './redirect-error'
export { notFound } from './not-found'
export { forbidden } from './forbidden'
export { unauthorized } from './unauthorized'
export { unstable_rethrow } from './unstable-rethrow'
export { ReadonlyURLSearchParams }

View File

@@ -0,0 +1,287 @@
import type { FlightRouterState } from '../../server/app-render/types'
import type { Params } from '../../server/request/params'
import { useContext, useMemo } from 'react'
import {
AppRouterContext,
LayoutRouterContext,
type AppRouterInstance,
} from '../../shared/lib/app-router-context.shared-runtime'
import {
SearchParamsContext,
PathnameContext,
PathParamsContext,
} from '../../shared/lib/hooks-client-context.shared-runtime'
import { getSegmentValue } from './router-reducer/reducers/get-segment-value'
import { PAGE_SEGMENT_KEY, DEFAULT_SEGMENT_KEY } from '../../shared/lib/segment'
import { ReadonlyURLSearchParams } from './navigation.react-server'
const useDynamicRouteParams =
typeof window === 'undefined'
? (
require('../../server/app-render/dynamic-rendering') as typeof import('../../server/app-render/dynamic-rendering')
).useDynamicRouteParams
: undefined
/**
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
* that lets you *read* the current URL's search parameters.
*
* Learn more about [`URLSearchParams` on MDN](https://developer.mozilla.org/docs/Web/API/URLSearchParams)
*
* @example
* ```ts
* "use client"
* import { useSearchParams } from 'next/navigation'
*
* export default function Page() {
* const searchParams = useSearchParams()
* searchParams.get('foo') // returns 'bar' when ?foo=bar
* // ...
* }
* ```
*
* Read more: [Next.js Docs: `useSearchParams`](https://nextjs.org/docs/app/api-reference/functions/use-search-params)
*/
// Client components API
export function useSearchParams(): ReadonlyURLSearchParams {
const searchParams = useContext(SearchParamsContext)
// In the case where this is `null`, the compat types added in
// `next-env.d.ts` will add a new overload that changes the return type to
// include `null`.
const readonlySearchParams = useMemo(() => {
if (!searchParams) {
// When the router is not ready in pages, we won't have the search params
// available.
return null
}
return new ReadonlyURLSearchParams(searchParams)
}, [searchParams]) as ReadonlyURLSearchParams
if (typeof window === 'undefined') {
// AsyncLocalStorage should not be included in the client bundle.
const { bailoutToClientRendering } =
require('./bailout-to-client-rendering') as typeof import('./bailout-to-client-rendering')
// TODO-APP: handle dynamic = 'force-static' here and on the client
bailoutToClientRendering('useSearchParams()')
}
return readonlySearchParams
}
/**
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
* that lets you read the current URL's pathname.
*
* @example
* ```ts
* "use client"
* import { usePathname } from 'next/navigation'
*
* export default function Page() {
* const pathname = usePathname() // returns "/dashboard" on /dashboard?foo=bar
* // ...
* }
* ```
*
* Read more: [Next.js Docs: `usePathname`](https://nextjs.org/docs/app/api-reference/functions/use-pathname)
*/
// Client components API
export function usePathname(): string {
useDynamicRouteParams?.('usePathname()')
// In the case where this is `null`, the compat types added in `next-env.d.ts`
// will add a new overload that changes the return type to include `null`.
return useContext(PathnameContext) as string
}
// Client components API
export {
ServerInsertedHTMLContext,
useServerInsertedHTML,
} from '../../shared/lib/server-inserted-html.shared-runtime'
/**
*
* This hook allows you to programmatically change routes inside [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components).
*
* @example
* ```ts
* "use client"
* import { useRouter } from 'next/navigation'
*
* export default function Page() {
* const router = useRouter()
* // ...
* router.push('/dashboard') // Navigate to /dashboard
* }
* ```
*
* Read more: [Next.js Docs: `useRouter`](https://nextjs.org/docs/app/api-reference/functions/use-router)
*/
// Client components API
export function useRouter(): AppRouterInstance {
const router = useContext(AppRouterContext)
if (router === null) {
throw new Error('invariant expected app router to be mounted')
}
return router
}
/**
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
* that lets you read a route's dynamic params filled in by the current URL.
*
* @example
* ```ts
* "use client"
* import { useParams } from 'next/navigation'
*
* export default function Page() {
* // on /dashboard/[team] where pathname is /dashboard/nextjs
* const { team } = useParams() // team === "nextjs"
* }
* ```
*
* Read more: [Next.js Docs: `useParams`](https://nextjs.org/docs/app/api-reference/functions/use-params)
*/
// Client components API
export function useParams<T extends Params = Params>(): T {
useDynamicRouteParams?.('useParams()')
return useContext(PathParamsContext) as T
}
/** Get the canonical parameters from the current level to the leaf node. */
// Client components API
function getSelectedLayoutSegmentPath(
tree: FlightRouterState,
parallelRouteKey: string,
first = true,
segmentPath: string[] = []
): string[] {
let node: FlightRouterState
if (first) {
// Use the provided parallel route key on the first parallel route
node = tree[1][parallelRouteKey]
} else {
// After first parallel route prefer children, if there's no children pick the first parallel route.
const parallelRoutes = tree[1]
node = parallelRoutes.children ?? Object.values(parallelRoutes)[0]
}
if (!node) return segmentPath
const segment = node[0]
let segmentValue = getSegmentValue(segment)
if (!segmentValue || segmentValue.startsWith(PAGE_SEGMENT_KEY)) {
return segmentPath
}
segmentPath.push(segmentValue)
return getSelectedLayoutSegmentPath(
node,
parallelRouteKey,
false,
segmentPath
)
}
/**
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
* that lets you read the active route segments **below** the Layout it is called from.
*
* @example
* ```ts
* 'use client'
*
* import { useSelectedLayoutSegments } from 'next/navigation'
*
* export default function ExampleClientComponent() {
* const segments = useSelectedLayoutSegments()
*
* return (
* <ul>
* {segments.map((segment, index) => (
* <li key={index}>{segment}</li>
* ))}
* </ul>
* )
* }
* ```
*
* Read more: [Next.js Docs: `useSelectedLayoutSegments`](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segments)
*/
// Client components API
export function useSelectedLayoutSegments(
parallelRouteKey: string = 'children'
): string[] {
useDynamicRouteParams?.('useSelectedLayoutSegments()')
const context = useContext(LayoutRouterContext)
// @ts-expect-error This only happens in `pages`. Type is overwritten in navigation.d.ts
if (!context) return null
return getSelectedLayoutSegmentPath(context.parentTree, parallelRouteKey)
}
/**
* A [Client Component](https://nextjs.org/docs/app/building-your-application/rendering/client-components) hook
* that lets you read the active route segment **one level below** the Layout it is called from.
*
* @example
* ```ts
* 'use client'
* import { useSelectedLayoutSegment } from 'next/navigation'
*
* export default function ExampleClientComponent() {
* const segment = useSelectedLayoutSegment()
*
* return <p>Active segment: {segment}</p>
* }
* ```
*
* Read more: [Next.js Docs: `useSelectedLayoutSegment`](https://nextjs.org/docs/app/api-reference/functions/use-selected-layout-segment)
*/
// Client components API
export function useSelectedLayoutSegment(
parallelRouteKey: string = 'children'
): string | null {
useDynamicRouteParams?.('useSelectedLayoutSegment()')
const selectedLayoutSegments = useSelectedLayoutSegments(parallelRouteKey)
if (!selectedLayoutSegments || selectedLayoutSegments.length === 0) {
return null
}
const selectedLayoutSegment =
parallelRouteKey === 'children'
? selectedLayoutSegments[0]
: selectedLayoutSegments[selectedLayoutSegments.length - 1]
// if the default slot is showing, we return null since it's not technically "selected" (it's a fallback)
// and returning an internal value like `__DEFAULT__` would be confusing.
return selectedLayoutSegment === DEFAULT_SEGMENT_KEY
? null
: selectedLayoutSegment
}
export { unstable_isUnrecognizedActionError } from './unrecognized-action-error'
// Shared components APIs
export {
notFound,
forbidden,
unauthorized,
redirect,
permanentRedirect,
RedirectType,
ReadonlyURLSearchParams,
unstable_rethrow,
} from './navigation.react-server'

View File

@@ -0,0 +1,29 @@
import {
HTTP_ERROR_FALLBACK_ERROR_CODE,
type HTTPAccessFallbackError,
} from './http-access-fallback/http-access-fallback'
/**
* This function allows you to render the [not-found.js file](https://nextjs.org/docs/app/api-reference/file-conventions/not-found)
* within a route segment as well as inject a tag.
*
* `notFound()` can be used in
* [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components),
* [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers), and
* [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
*
* - In a Server Component, this will insert a `<meta name="robots" content="noindex" />` meta tag and set the status code to 404.
* - In a Route Handler or Server Action, it will serve a 404 to the caller.
*
* Read more: [Next.js Docs: `notFound`](https://nextjs.org/docs/app/api-reference/functions/not-found)
*/
const DIGEST = `${HTTP_ERROR_FALLBACK_ERROR_CODE};404`
export function notFound(): never {
// eslint-disable-next-line no-throw-literal
const error = new Error(DIGEST) as HTTPAccessFallbackError
;(error as HTTPAccessFallbackError).digest = DIGEST
throw error
}

View File

@@ -0,0 +1,99 @@
import { RedirectStatusCode } from './redirect-status-code'
import {
RedirectType,
type RedirectError,
isRedirectError,
REDIRECT_ERROR_CODE,
} from './redirect-error'
const actionAsyncStorage =
typeof window === 'undefined'
? (
require('../../server/app-render/action-async-storage.external') as typeof import('../../server/app-render/action-async-storage.external')
).actionAsyncStorage
: undefined
export function getRedirectError(
url: string,
type: RedirectType,
statusCode: RedirectStatusCode = RedirectStatusCode.TemporaryRedirect
): RedirectError {
const error = new Error(REDIRECT_ERROR_CODE) as RedirectError
error.digest = `${REDIRECT_ERROR_CODE};${type};${url};${statusCode};`
return error
}
/**
* This function allows you to redirect the user to another URL. It can be used in
* [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components),
* [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers), and
* [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
*
* - In a Server Component, this will insert a meta tag to redirect the user to the target page.
* - In a Route Handler or Server Action, it will serve a 307/303 to the caller.
* - In a Server Action, type defaults to 'push' and 'replace' elsewhere.
*
* Read more: [Next.js Docs: `redirect`](https://nextjs.org/docs/app/api-reference/functions/redirect)
*/
export function redirect(
/** The URL to redirect to */
url: string,
type?: RedirectType
): never {
type ??= actionAsyncStorage?.getStore()?.isAction
? RedirectType.push
: RedirectType.replace
throw getRedirectError(url, type, RedirectStatusCode.TemporaryRedirect)
}
/**
* This function allows you to redirect the user to another URL. It can be used in
* [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components),
* [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers), and
* [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
*
* - In a Server Component, this will insert a meta tag to redirect the user to the target page.
* - In a Route Handler or Server Action, it will serve a 308/303 to the caller.
*
* Read more: [Next.js Docs: `redirect`](https://nextjs.org/docs/app/api-reference/functions/redirect)
*/
export function permanentRedirect(
/** The URL to redirect to */
url: string,
type: RedirectType = RedirectType.replace
): never {
throw getRedirectError(url, type, RedirectStatusCode.PermanentRedirect)
}
/**
* Returns the encoded URL from the error if it's a RedirectError, null
* otherwise. Note that this does not validate the URL returned.
*
* @param error the error that may be a redirect error
* @return the url if the error was a redirect error
*/
export function getURLFromRedirectError(error: RedirectError): string
export function getURLFromRedirectError(error: unknown): string | null {
if (!isRedirectError(error)) return null
// Slices off the beginning of the digest that contains the code and the
// separating ';'.
return error.digest.split(';').slice(2, -2).join(';')
}
export function getRedirectTypeFromError(error: RedirectError): RedirectType {
if (!isRedirectError(error)) {
throw new Error('Not a redirect error')
}
return error.digest.split(';', 2)[1] as RedirectType
}
export function getRedirectStatusCodeFromError(error: RedirectError): number {
if (!isRedirectError(error)) {
throw new Error('Not a redirect error')
}
return Number(error.digest.split(';').at(-2))
}

View File

@@ -0,0 +1,5 @@
import type { Segment } from '../../../../server/app-render/types'
export function getSegmentValue(segment: Segment) {
return Array.isArray(segment) ? segment[1] : segment
}

View File

@@ -0,0 +1,34 @@
import {
HTTP_ERROR_FALLBACK_ERROR_CODE,
type HTTPAccessFallbackError,
} from './http-access-fallback/http-access-fallback'
// TODO: Add `unauthorized` docs
/**
* @experimental
* This function allows you to render the [unauthorized.js file](https://nextjs.org/docs/app/api-reference/file-conventions/unauthorized)
* within a route segment as well as inject a tag.
*
* `unauthorized()` can be used in
* [Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components),
* [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers), and
* [Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
*
*
* Read more: [Next.js Docs: `unauthorized`](https://nextjs.org/docs/app/api-reference/functions/unauthorized)
*/
const DIGEST = `${HTTP_ERROR_FALLBACK_ERROR_CODE};401`
export function unauthorized(): never {
if (!process.env.__NEXT_EXPERIMENTAL_AUTH_INTERRUPTS) {
throw new Error(
`\`unauthorized()\` is experimental and only allowed to be used when \`experimental.authInterrupts\` is enabled.`
)
}
// eslint-disable-next-line no-throw-literal
const error = new Error(DIGEST) as HTTPAccessFallbackError
;(error as HTTPAccessFallbackError).digest = DIGEST
throw error
}

View File

@@ -0,0 +1,34 @@
export class UnrecognizedActionError extends Error {
constructor(...args: ConstructorParameters<typeof Error>) {
super(...args)
this.name = 'UnrecognizedActionError'
}
}
/**
* Check whether a server action call failed because the server action was not recognized by the server.
* This can happen if the client and the server are not from the same deployment.
*
* Example usage:
* ```ts
* try {
* await myServerAction();
* } catch (err) {
* if (unstable_isUnrecognizedActionError(err)) {
* // The client is from a different deployment than the server.
* // Reloading the page will fix this mismatch.
* window.alert("Please refresh the page and try again");
* return;
* }
* }
* ```
* */
export function unstable_isUnrecognizedActionError(
error: unknown
): error is UnrecognizedActionError {
return !!(
error &&
typeof error === 'object' &&
error instanceof UnrecognizedActionError
)
}

View File

@@ -0,0 +1,12 @@
import { isBailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr'
import { isNextRouterError } from './is-next-router-error'
export function unstable_rethrow(error: unknown): void {
if (isNextRouterError(error) || isBailoutToCSRError(error)) {
throw error
}
if (error instanceof Error && 'cause' in error) {
unstable_rethrow(error.cause)
}
}

View File

@@ -0,0 +1,15 @@
/**
* This function should be used to rethrow internal Next.js errors so that they can be handled by the framework.
* When wrapping an API that uses errors to interrupt control flow, you should use this function before you do any error handling.
* This function will rethrow the error if it is a Next.js error so it can be handled, otherwise it will do nothing.
*
* Read more: [Next.js Docs: `unstable_rethrow`](https://nextjs.org/docs/app/api-reference/functions/unstable_rethrow)
*/
export const unstable_rethrow =
typeof window === 'undefined'
? (
require('./unstable-rethrow.server') as typeof import('./unstable-rethrow.server')
).unstable_rethrow
: (
require('./unstable-rethrow.browser') as typeof import('./unstable-rethrow.browser')
).unstable_rethrow

View File

@@ -0,0 +1,14 @@
export const getSafetyPath = (fileUrl) =>
fileUrl
.replace(/\\/g, '/')
.split('graphql/services/')
.pop()
.replace(/\..*?$/, '');
export const createModuleNameFromMetaUrl = (fileUrl) =>
fileUrl
.split(/graphql[\\/]services[\\/]/)
.pop()
.replace(/\..*?$/, '')
.replace(/[\\/]constants[\\/]index/, '')
.replace(/[\\/]/g, '_');

View File

@@ -0,0 +1,14 @@
import { createModuleNameFromMetaUrl } from '../../_helpers/common';
const MODULE_NAME = createModuleNameFromMetaUrl(import.meta.url);
// queries
export const GET_ACCOUNTS = `${MODULE_NAME}_GET_ACCOUNTS`;
export const GET_ACCOUNT = `${MODULE_NAME}_GET_ACCOUNT`;
export const GET_SALESFORCE_DATA = `${MODULE_NAME}_GET_SALESFORCE_DATA`;
export const GET_CONTENT_OWNERS = `${MODULE_NAME}_GET_CONTENT_OWNERS`;
// mutation
export const UPDATE_ACCOUNT = `${MODULE_NAME}_UPDATE_ACCOUNT`;
export const CREATE_ACCOUNT = `${MODULE_NAME}_CREATE_ACCOUNT`;
export const UPDATE_CONTENT_OWNER = `${MODULE_NAME}_UPDATE_CONTENT_OWNER`;
export const DELETE_ALL_VIDEOS = `${MODULE_NAME}_DELETE_ALL_VIDEOS`;

View File

@@ -0,0 +1,14 @@
import { GraphQLInputObjectType, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: PAYLOAD_NAME,
fields: {
id: {
type: GraphQLString,
},
},
});

View File

@@ -0,0 +1,29 @@
import { GraphQLInputObjectType, GraphQLInt, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: PAYLOAD_NAME,
fields: {
sortBy: {
type: GraphQLString,
},
sortOrder: {
type: GraphQLString,
},
page: {
type: GraphQLInt,
},
pageSize: {
type: GraphQLInt,
},
searchText: {
type: GraphQLString,
},
type: {
type: GraphQLString,
},
},
});

View File

@@ -0,0 +1,38 @@
import { GraphQLInputObjectType, GraphQLInt, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: PAYLOAD_NAME,
fields: {
accountId: {
type: GraphQLString,
},
id: {
type: GraphQLInt,
},
name: {
type: GraphQLString,
},
status: {
type: GraphQLInt,
},
type: {
type: GraphQLInt,
},
comments: {
type: GraphQLString,
},
salesforceId: {
type: GraphQLString,
},
callToAction: {
type: GraphQLInt,
},
isPublic: {
type: GraphQLInt,
},
},
});

View File

@@ -0,0 +1,26 @@
import { GraphQLInputObjectType, GraphQLInt, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: PAYLOAD_NAME,
fields: {
accountId: {
type: GraphQLString,
},
sortBy: {
type: GraphQLString,
},
sortOrder: {
type: GraphQLString,
},
page: {
type: GraphQLInt,
},
pageSize: {
type: GraphQLInt,
},
},
});

View File

@@ -0,0 +1,14 @@
import { GraphQLInputObjectType, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: PAYLOAD_NAME,
fields: {
id: {
type: GraphQLString,
},
},
});

View File

@@ -0,0 +1,211 @@
import { GraphQLBoolean, GraphQLFloat, GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: PAYLOAD_NAME,
fields: {
id: {
type: GraphQLString,
},
name: {
type: GraphQLString,
},
salesforceId: {
type: GraphQLString,
},
type: {
type: GraphQLString,
},
accountManager: {
type: GraphQLString,
},
accountManagerEmail: {
type: GraphQLString,
},
publisherDemand: {
type: GraphQLBoolean,
},
adServingFeeBusinessModel: {
type: GraphQLString,
},
adServingFeeDisplay: {
type: GraphQLFloat,
},
adServingFeeVideo: {
type: GraphQLFloat,
},
salesManager: {
type: GraphQLString,
},
customLogoUrl: {
type: GraphQLString,
},
avatarUrl: {
type: GraphQLString,
},
avatar: {
type: new GraphQLInputObjectType({
name: `${PAYLOAD_NAME}accountsAvatar`,
fields: {
base64Url: {
type: GraphQLString,
},
mimeType: {
type: GraphQLString,
},
},
}),
},
customLoadingMessage: {
type: GraphQLString,
},
customLoginPageUrl: {
type: GraphQLString,
},
subdomain: {
type: GraphQLString,
},
publisherRevShare: {
type: GraphQLInt,
},
expenses: {
type: GraphQLInt,
},
usersLimit: {
type: GraphQLInt,
},
videoDuplicatesBy: {
type: GraphQLString,
},
accountsFeatures: {
type: new GraphQLList(
new GraphQLInputObjectType({
name: `${PAYLOAD_NAME}accountsFeatures`,
fields: {
id: {
type: GraphQLInt,
},
value: {
type: GraphQLBoolean,
},
},
}),
),
},
accountDashboards: {
type: new GraphQLInputObjectType({
name: `${PAYLOAD_NAME}accountDashboards`,
fields: {
general: {
type: new GraphQLList(
new GraphQLInputObjectType({
name: `${PAYLOAD_NAME}accountDashboardsGeneral`,
fields: {
id: {
type: GraphQLInt,
},
uiName: {
type: GraphQLString,
},
enabled: {
type: GraphQLBoolean,
},
order: {
type: GraphQLInt,
},
tooltip: {
type: new GraphQLInputObjectType({
name: `${PAYLOAD_NAME}accountDashboardsGeneralTooltip`,
fields: {
info: {
type: GraphQLString,
},
link: {
type: GraphQLString,
},
linkTitle: {
type: GraphQLString,
},
},
}),
},
},
}),
),
},
custom: {
type: new GraphQLList(
new GraphQLInputObjectType({
name: `${PAYLOAD_NAME}accountDashboardsCustom`,
fields: {
id: {
type: GraphQLInt,
},
uiName: {
type: GraphQLString,
},
enabled: {
type: GraphQLBoolean,
},
order: {
type: GraphQLInt,
},
lookerReportId: {
type: GraphQLString,
},
icon: {
type: GraphQLString,
},
tooltip: {
type: new GraphQLInputObjectType({
name: `${PAYLOAD_NAME}accountDashboardsCustomTooltip`,
fields: {
info: {
type: GraphQLString,
},
link: {
type: GraphQLString,
},
linkTitle: {
type: GraphQLString,
},
},
}),
},
publisherCustomAnalytics: {
type: new GraphQLInputObjectType({
name: `${PAYLOAD_NAME}accountDashboardsCustomPublisherCustomAnalytics`,
fields: {
accountId: {
type: GraphQLInt,
},
action: {
type: GraphQLString,
},
analyticsId: {
type: GraphQLInt,
},
id: {
type: GraphQLInt,
},
publisherId: {
type: GraphQLInt,
},
publisherIds: {
type: new GraphQLList(GraphQLInt),
},
},
}),
},
},
}),
),
},
},
}),
},
},
});

View File

@@ -0,0 +1,14 @@
import { GraphQLInputObjectType, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: PAYLOAD_NAME,
fields: {
id: {
type: GraphQLString,
},
},
});

View File

@@ -0,0 +1,5 @@
import { createModuleNameFromMetaUrl } from '../../_helpers/common';
const MODULE_NAME = createModuleNameFromMetaUrl(import.meta.url);
export const BULK_CHANGE_STATUS_ACTION = `${MODULE_NAME}_BULK_CHANGE_STATUS_ACTION`;

View File

@@ -0,0 +1,39 @@
import { GraphQLBoolean, GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLString } from 'graphql';
export const ITEM_INPUT_TYPE_NAME = 'AdServersPCNInputType';
const CreateInputType = new GraphQLInputObjectType({
name: ITEM_INPUT_TYPE_NAME,
description: 'Module AdServersPCN Create Input Type',
fields: {
id: {
type: GraphQLInt,
},
name: {
type: GraphQLString,
},
status: {
type: GraphQLInt,
},
url: {
type: GraphQLString,
},
placementId: {
type: GraphQLInt,
},
macro: {
type: GraphQLString,
},
comments: {
type: GraphQLString,
},
isDefaultForPlayerType: {
type: GraphQLBoolean,
},
playerTypesIds: {
type: new GraphQLList(GraphQLInt),
},
},
});
export default CreateInputType;

View File

@@ -0,0 +1,17 @@
import { GraphQLInputObjectType, GraphQLInt, GraphQLList } from 'graphql';
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: PAYLOAD_NAME,
fields: {
ids: {
type: new GraphQLList(GraphQLInt),
},
status: {
type: GraphQLInt,
},
},
});

View File

@@ -0,0 +1,12 @@
import { createModuleNameFromMetaUrl } from '../../_helpers/common';
const MODULE_NAME = createModuleNameFromMetaUrl(import.meta.url);
// queries
export const GET_ADVERTISERS = `${MODULE_NAME}_GET_ADVERTISERS`;
export const GET_ADVERTISER = `${MODULE_NAME}_GET_ADVERTISER`;
export const GET_ADVERTISER_DM_ACCOUNTS_OPTIONS = `${MODULE_NAME}GET_ADVERTISER_DM_ACCOUNTS_OPTIONS`;
// mutations
export const CREATE_ADVERTISER = `${MODULE_NAME}_CREATE_ADVERTISER`;
export const UPDATE_ADVERTISER = `${MODULE_NAME}_UPDATE_ADVERTISER`;

View File

@@ -0,0 +1,14 @@
import { GraphQLInputObjectType, GraphQLInt } from 'graphql';
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: PAYLOAD_NAME,
fields: {
id: {
type: GraphQLInt,
},
},
});

View File

@@ -0,0 +1,26 @@
import { GraphQLInputObjectType, GraphQLInt, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: PAYLOAD_NAME,
fields: {
sortBy: {
type: GraphQLString,
},
sortOrder: {
type: GraphQLString,
},
page: {
type: GraphQLInt,
},
pageSize: {
type: GraphQLInt,
},
searchText: {
type: GraphQLString,
},
},
});

View File

@@ -0,0 +1,20 @@
import { GraphQLInputObjectType, GraphQLInt, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '../../../_helpers/common';
export const PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: PAYLOAD_NAME,
fields: {
id: {
type: GraphQLInt,
},
name: {
type: GraphQLString,
},
demandAccountId: {
type: GraphQLInt,
},
},
});

View File

@@ -0,0 +1,9 @@
import { createModuleNameFromMetaUrl } from '../../_helpers/common';
const MODULE_NAME = createModuleNameFromMetaUrl(import.meta.url);
// queries
export const GET_AIW_TAGLOG_DATA = `${MODULE_NAME}_GET_AIW_TAGLOG_DATA`;
export const GET_AIW_TAGLOG_TAG_INFO = `${MODULE_NAME}_GET_AIW_TAGLOG_TAG_INFO`;
// mutations
export const UPSERT_TAGS = `${MODULE_NAME}_UPSERT_TAGS`;

View File

@@ -0,0 +1,11 @@
import { createModuleNameFromMetaUrl } from '../../_helpers/common';
const MODULE_NAME = createModuleNameFromMetaUrl(import.meta.url);
// queries
export const GET_AIW_THUMBNAIL_DATA = `${MODULE_NAME}_GET_AIW_THUMBNAIL_DATA`;
// mutations
export const GENERATE_AIW_THUMBNAIL_OPTIONS = `${MODULE_NAME}_GENERATE_AIW_THUMBNAIL_OPTIONS`;
export const SET_AIW_THUMBNAIL_DRAFT = `${MODULE_NAME}_SET_AIW_THUMBNAIL_DRAFT`;
export const PUBLISH_AIW_THUMBNAIL = `${MODULE_NAME}_PUBLISH_AIW_THUMBNAIL`;
export const SET_AIW_THUMBNAIL_FROM_VIDEO_FRAME = `${MODULE_NAME}_SET_AIW_THUMBNAIL_FROM_VIDEO_FRAME`;

View File

@@ -0,0 +1,23 @@
import { GraphQLInputObjectType, GraphQLInt, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '@/graphql/services/_helpers/common';
export const GET_AIW_TAGLOG_DATA_PAYLOAD = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: GET_AIW_TAGLOG_DATA_PAYLOAD,
fields: {
videoId: {
type: GraphQLString,
},
startTime: {
type: GraphQLInt,
},
next: {
type: GraphQLInt,
},
previous: {
type: GraphQLInt,
},
},
});

View File

@@ -0,0 +1,32 @@
import { GraphQLBoolean, GraphQLInputObjectType, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '@/graphql/services/_helpers/common';
export const GET_AIW_TAGLOG_TAG_INFO_PAYLOAD = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: GET_AIW_TAGLOG_TAG_INFO_PAYLOAD,
fields: {
videoId: {
type: GraphQLString,
},
tagId: {
type: GraphQLString,
},
uid: {
type: GraphQLString,
},
labelId: {
type: GraphQLString,
},
labelValue: {
type: GraphQLString,
},
text: {
type: GraphQLString,
},
isIab: {
type: GraphQLBoolean,
},
},
});

View File

@@ -0,0 +1,47 @@
import { GraphQLInputObjectType, GraphQLInt, GraphQLList, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '../../../../_helpers/common';
export const UPSERT_TAGS_PAYLOAD_NAME = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: UPSERT_TAGS_PAYLOAD_NAME,
fields: {
videoId: {
type: GraphQLString,
},
logRowUid: {
type: GraphQLString,
},
startTime: {
type: GraphQLInt,
},
keywords: {
type: new GraphQLList(
new GraphQLInputObjectType({
name: `${UPSERT_TAGS_PAYLOAD_NAME}_keywords`,
fields: {
category: {
type: GraphQLString,
},
value: {
type: GraphQLString,
},
id: {
type: GraphQLString,
},
labelId: {
type: GraphQLString,
},
labelName: {
type: GraphQLString,
},
color: {
type: GraphQLString,
},
},
}),
),
},
},
});

View File

@@ -0,0 +1,17 @@
import { GraphQLBoolean, GraphQLInputObjectType, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '@/graphql/services/_helpers/common';
export const GENERATE_AIW_THUMBNAIL_OPTIONS_PAYLOAD = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: GENERATE_AIW_THUMBNAIL_OPTIONS_PAYLOAD,
fields: {
videoId: {
type: GraphQLString,
},
notifyByEmail: {
type: GraphQLBoolean,
},
},
});

View File

@@ -0,0 +1,17 @@
import { GraphQLBoolean, GraphQLInputObjectType, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '@/graphql/services/_helpers/common';
export const PUBLISH_AIW_THUMBNAIL_PAYLOAD = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: PUBLISH_AIW_THUMBNAIL_PAYLOAD,
fields: {
videoId: {
type: GraphQLString,
},
publish: {
type: GraphQLBoolean,
},
},
});

View File

@@ -0,0 +1,20 @@
import { GraphQLInputObjectType, GraphQLString } from 'graphql';
import { createModuleNameFromMetaUrl } from '@/graphql/services/_helpers/common';
export const SET_AIW_THUMBNAIL_DRAFT_PAYLOAD = createModuleNameFromMetaUrl(import.meta.url);
export default new GraphQLInputObjectType({
name: SET_AIW_THUMBNAIL_DRAFT_PAYLOAD,
fields: {
videoId: {
type: GraphQLString,
},
uid: {
type: GraphQLString,
},
thumbnail: {
type: GraphQLString,
},
},
});

Some files were not shown because too many files have changed in this diff Show More