mirror of
https://github.com/documenso/documenso.git
synced 2025-11-23 13:11:32 +10:00
feat: migrate nextjs to rr7
This commit is contained in:
78
apps/remix/app/utils/css-vars.ts
Normal file
78
apps/remix/app/utils/css-vars.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { colord } from 'colord';
|
||||
import { toKebabCase } from 'remeda';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const ZCssVarsSchema = z
|
||||
.object({
|
||||
background: z.string().optional().describe('Base background color'),
|
||||
foreground: z.string().optional().describe('Base text color'),
|
||||
muted: z.string().optional().describe('Muted/subtle background color'),
|
||||
mutedForeground: z.string().optional().describe('Muted/subtle text color'),
|
||||
popover: z.string().optional().describe('Popover/dropdown background color'),
|
||||
popoverForeground: z.string().optional().describe('Popover/dropdown text color'),
|
||||
card: z.string().optional().describe('Card background color'),
|
||||
cardBorder: z.string().optional().describe('Card border color'),
|
||||
cardBorderTint: z.string().optional().describe('Card border tint/highlight color'),
|
||||
cardForeground: z.string().optional().describe('Card text color'),
|
||||
fieldCard: z.string().optional().describe('Field card background color'),
|
||||
fieldCardBorder: z.string().optional().describe('Field card border color'),
|
||||
fieldCardForeground: z.string().optional().describe('Field card text color'),
|
||||
widget: z.string().optional().describe('Widget background color'),
|
||||
widgetForeground: z.string().optional().describe('Widget text color'),
|
||||
border: z.string().optional().describe('Default border color'),
|
||||
input: z.string().optional().describe('Input field border color'),
|
||||
primary: z.string().optional().describe('Primary action/button color'),
|
||||
primaryForeground: z.string().optional().describe('Primary action/button text color'),
|
||||
secondary: z.string().optional().describe('Secondary action/button color'),
|
||||
secondaryForeground: z.string().optional().describe('Secondary action/button text color'),
|
||||
accent: z.string().optional().describe('Accent/highlight color'),
|
||||
accentForeground: z.string().optional().describe('Accent/highlight text color'),
|
||||
destructive: z.string().optional().describe('Destructive/danger action color'),
|
||||
destructiveForeground: z.string().optional().describe('Destructive/danger text color'),
|
||||
ring: z.string().optional().describe('Focus ring color'),
|
||||
radius: z.string().optional().describe('Border radius size in REM units'),
|
||||
warning: z.string().optional().describe('Warning/alert color'),
|
||||
})
|
||||
.describe('Custom CSS variables for theming');
|
||||
|
||||
export type TCssVarsSchema = z.infer<typeof ZCssVarsSchema>;
|
||||
|
||||
export const toNativeCssVars = (vars: TCssVarsSchema) => {
|
||||
const cssVars: Record<string, string> = {};
|
||||
|
||||
const { radius, ...colorVars } = vars;
|
||||
|
||||
for (const [key, value] of Object.entries(colorVars)) {
|
||||
if (value) {
|
||||
const color = colord(value);
|
||||
const { h, s, l } = color.toHsl();
|
||||
|
||||
cssVars[`--${toKebabCase(key)}`] = `${h} ${s} ${l}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (radius) {
|
||||
cssVars[`--radius`] = `${radius}`;
|
||||
}
|
||||
|
||||
return cssVars;
|
||||
};
|
||||
|
||||
export const injectCss = (options: { css?: string; cssVars?: TCssVarsSchema }) => {
|
||||
const { css, cssVars } = options;
|
||||
|
||||
if (css) {
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = css;
|
||||
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
if (cssVars) {
|
||||
const nativeVars = toNativeCssVars(cssVars);
|
||||
|
||||
for (const [key, value] of Object.entries(nativeVars)) {
|
||||
document.documentElement.style.setProperty(key, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
16
apps/remix/app/utils/get-asset-buffer.ts
Normal file
16
apps/remix/app/utils/get-asset-buffer.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
|
||||
/**
|
||||
* getAssetBuffer is used to retrieve array buffers for various assets
|
||||
* that are hosted in the `public` folder.
|
||||
*
|
||||
* This exists due to a breakage with `import.meta.url` imports and open graph images,
|
||||
* once we can identify a fix for this, we can remove this helper.
|
||||
*
|
||||
* @param path The path to the asset, relative to the `public` folder.
|
||||
*/
|
||||
export const getAssetBuffer = async (path: string) => {
|
||||
const baseUrl = NEXT_PUBLIC_WEBAPP_URL() || 'http://localhost:3000';
|
||||
|
||||
return fetch(new URL(path, baseUrl)).then(async (res) => res.arrayBuffer());
|
||||
};
|
||||
61
apps/remix/app/utils/meta.ts
Normal file
61
apps/remix/app/utils/meta.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
|
||||
export const appMetaTags = (title?: string) => {
|
||||
const description =
|
||||
'Join Documenso, the open signing infrastructure, and get a 10x better signing experience. Pricing starts at $30/mo. forever! Sign in now and enjoy a faster, smarter, and more beautiful document signing process. Integrates with your favorite tools, customizable, and expandable. Support our mission and become a part of our open-source community.';
|
||||
|
||||
return [
|
||||
{
|
||||
title: title ? `${title} - Documenso` : 'Documenso',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
content: description,
|
||||
},
|
||||
{
|
||||
name: 'keywords',
|
||||
content:
|
||||
'Documenso, open source, DocuSign alternative, document signing, open signing infrastructure, open-source community, fast signing, beautiful signing, smart templates',
|
||||
},
|
||||
{
|
||||
name: 'author',
|
||||
content: 'Documenso, Inc.',
|
||||
},
|
||||
{
|
||||
name: 'robots',
|
||||
content: 'index, follow',
|
||||
},
|
||||
{
|
||||
property: 'og:title',
|
||||
content: 'Documenso - The Open Source DocuSign Alternative',
|
||||
},
|
||||
{
|
||||
property: 'og:description',
|
||||
content: description,
|
||||
},
|
||||
{
|
||||
property: 'og:image',
|
||||
content: `${NEXT_PUBLIC_WEBAPP_URL()}/opengraph-image.jpg`,
|
||||
},
|
||||
{
|
||||
property: 'og:type',
|
||||
content: 'website',
|
||||
},
|
||||
{
|
||||
name: 'twitter:card',
|
||||
content: 'summary_large_image',
|
||||
},
|
||||
{
|
||||
name: 'twitter:site',
|
||||
content: '@documenso',
|
||||
},
|
||||
{
|
||||
name: 'twitter:description',
|
||||
content: description,
|
||||
},
|
||||
{
|
||||
name: 'twitter:image',
|
||||
content: `${NEXT_PUBLIC_WEBAPP_URL()}/opengraph-image.jpg`,
|
||||
},
|
||||
];
|
||||
};
|
||||
57
apps/remix/app/utils/super-json-loader.ts
Normal file
57
apps/remix/app/utils/super-json-loader.ts
Normal file
@ -0,0 +1,57 @@
|
||||
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
/**
|
||||
* https://github.com/kiliman/remix-superjson/
|
||||
*/
|
||||
import { useActionData, useLoaderData } from 'react-router';
|
||||
import * as _superjson from 'superjson';
|
||||
|
||||
export type SuperJsonFunction = <Data extends unknown>(
|
||||
data: Data,
|
||||
init?: number | ResponseInit,
|
||||
) => SuperTypedResponse<Data>;
|
||||
|
||||
export declare type SuperTypedResponse<T extends unknown = unknown> = Response & {
|
||||
superjson(): Promise<T>;
|
||||
};
|
||||
|
||||
type AppData = any;
|
||||
type DataFunction = (...args: any[]) => unknown; // matches any function
|
||||
type DataOrFunction = AppData | DataFunction;
|
||||
|
||||
export type UseDataFunctionReturn<T extends DataOrFunction> = T extends (
|
||||
...args: any[]
|
||||
) => infer Output
|
||||
? Awaited<Output> extends SuperTypedResponse<infer U>
|
||||
? U
|
||||
: Awaited<ReturnType<T>>
|
||||
: Awaited<T>;
|
||||
|
||||
export const superLoaderJson: SuperJsonFunction = (data, init = {}) => {
|
||||
const responseInit = typeof init === 'number' ? { status: init } : init;
|
||||
const headers = new Headers(responseInit.headers);
|
||||
|
||||
if (!headers.has('Content-Type')) {
|
||||
headers.set('Content-Type', 'application/json; charset=utf-8');
|
||||
}
|
||||
|
||||
return new Response(_superjson.stringify(data), {
|
||||
...responseInit,
|
||||
headers,
|
||||
}) as SuperTypedResponse<typeof data>;
|
||||
};
|
||||
|
||||
export function useSuperLoaderData<T = AppData>(): UseDataFunctionReturn<T> {
|
||||
const data = useLoaderData();
|
||||
|
||||
return _superjson.deserialize(data);
|
||||
}
|
||||
export function useSuperActionData<T = AppData>(): UseDataFunctionReturn<T> | null {
|
||||
const data = useActionData();
|
||||
|
||||
return data ? _superjson.deserialize(data) : null;
|
||||
}
|
||||
10
apps/remix/app/utils/truncate-title.ts
Normal file
10
apps/remix/app/utils/truncate-title.ts
Normal file
@ -0,0 +1,10 @@
|
||||
export const truncateTitle = (title: string, maxLength: number = 16) => {
|
||||
if (title.length <= maxLength) {
|
||||
return title;
|
||||
}
|
||||
|
||||
const start = title.slice(0, maxLength / 2);
|
||||
const end = title.slice(-maxLength / 2);
|
||||
|
||||
return `${start}.....${end}`;
|
||||
};
|
||||
Reference in New Issue
Block a user