chore: tailwind 4 update (#1085)

* WIP

* WIP

* WIP

* fixed css for login page

* WIP

* working version

* cleanup

* aligned colors
This commit is contained in:
Deepak Prabhakara 2025-12-15 11:29:26 +00:00 committed by GitHub
parent 05ec6bce12
commit 0aceeb148f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 759 additions and 1115 deletions

View File

@ -35,7 +35,7 @@ export default function Layout({ children }: LayoutProps) {
<meta name='twitter:image' content={ogImage} />
</Head>
<Header />
<main className='h-[calc(100%_-_80px)] overflow-auto py-5'>{children}</main>
<main className='h-[calc(100%-80px)] overflow-auto py-5'>{children}</main>
</>
);
}

1465
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,18 +15,17 @@
},
"dependencies": {
"@boxyhq/saml20": "1.12.2",
"daisyui": "4.12.24",
"next": "16.0.10",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-gtm-module": "2.0.11"
},
"devDependencies": {
"@tailwindcss/postcss": "4.1.18",
"@types/node": "25.0.1",
"@types/react": "19.2.7",
"@types/react-gtm-module": "2.0.4",
"@typescript-eslint/parser": "8.49.0",
"autoprefixer": "10.4.22",
"eslint": "9.39.1",
"eslint-config-next": "16.0.10",
"eslint-config-prettier": "10.1.8",
@ -36,7 +35,7 @@
"prettier": "3.7.4",
"prettier-plugin-tailwindcss": "0.7.2",
"release-it": "19.1.0",
"tailwindcss": "3.4.17",
"tailwindcss": "4.1.17",
"typescript": "5.9.3"
},
"engines": {

View File

@ -7,76 +7,93 @@ import { getEntityId, getSSOUrl } from 'lib/entity-id';
const Home: React.FC<{ metadata: IdPMetadata; params: any }> = ({ metadata, params }) => {
const namespace = params.namespace;
const { ssoUrl: appUrl, entityId, certificate } = metadata;
const namespaceEntityId = getEntityId(entityId, namespace);
const metadataDownloadUrl =
'/api' + (namespace ? `/namespace/${namespace}` : '') + '/saml/metadata?download=true';
const metadataUrl = '/api' + (namespace ? `/namespace/${namespace}` : '') + '/saml/metadata';
const loginUrl = (namespace ? `/namespace/${namespace}` : '') + '/saml/login';
const ssoUrl = getSSOUrl(appUrl, namespace);
return (
<div className='flex items-center justify-center'>
<div className='flex w-full max-w-4xl flex-col space-y-5 px-2'>
<h1 className='text-center text-xl font-extrabold text-gray-900 md:text-2xl'>
<div className='flex min-h-screen justify-center bg-white pt-12'>
<div className='w-full max-w-4xl px-2 space-y-6'>
<h1 className='text-center text-xl font-semibold text-gray-900 md:text-2xl'>
A free SAML 2.0 Identity Provider for testing SAML SSO integrations.
</h1>
<div className='flex flex-col justify-between space-y-5 md:flex-row md:space-y-0'>
<div className='flex flex-col space-y-5 md:flex-row md:space-x-5 md:space-y-0'>
<Link href={metadataDownloadUrl} className='btn-primary btn-active btn'>
<svg
className='mr-1 inline-block h-6 w-6'
fill='none'
viewBox='0 0 24 24'
stroke='currentColor'
aria-hidden
strokeWidth='2'>
<path
strokeLinecap='round'
strokeLinejoin='round'
d='M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4'
/>
</svg>
{/* Actions */}
<div className='flex flex-col gap-4 md:flex-row md:items-center md:justify-between'>
<div className='flex flex-col gap-3 md:flex-row'>
<Link
href={metadataDownloadUrl}
className='inline-flex items-center justify-center rounded-md
bg-primary px-4 py-2 text-sm font-semibold text-white
hover:bg-primary-hover
focus:outline-none focus:ring-2 focus:ring-primary-ring'>
Download Metadata
</Link>
<Link href={metadataUrl} className='btn-outline btn-primary btn' target='_blank'>
<Link
href={metadataUrl}
target='_blank'
className='inline-flex items-center justify-center rounded-md
border border-primary px-4 py-2 text-sm font-semibold
text-primary hover:bg-primary-soft
focus:outline-none focus:ring-2 focus:ring-primary-ring'>
Metadata URL
</Link>
</div>
<Link href={loginUrl} className='btn-outline btn-primary btn'>
<Link
href={loginUrl}
className='inline-flex items-center justify-center rounded-md
border border-primary px-4 py-2 text-sm font-semibold
text-primary hover:bg-primary-soft
focus:outline-none focus:ring-2 focus:ring-primary-ring'>
Test IdP Login
</Link>
</div>
<div className='border-2 p-3'>
<h2 className='mb-5 text-center text-2xl font-bold text-gray-900'>Mock SAML Metadata</h2>
<div className='grid grid-cols-2 gap-y-5 gap-x-5'>
<div className='form-control'>
<label className='label'>
<span className='label-text font-bold'>SSO URL</span>
</label>
<input type='text' defaultValue={ssoUrl} className='input-bordered input' disabled />
{/* Metadata Card */}
<div className='rounded-lg border border-gray-200 bg-white p-4 shadow-sm'>
<h2 className='mb-5 text-center text-2xl font-semibold text-gray-900'>Mock SAML Metadata</h2>
<div className='grid grid-cols-1 gap-5 md:grid-cols-2'>
<div>
<label className='block mb-1 text-sm font-medium text-gray-700'>SSO URL</label>
<input
type='text'
defaultValue={ssoUrl}
disabled
className='w-full rounded-md border border-gray-300 bg-gray-100 px-3 py-2 text-sm text-gray-700'
/>
</div>
<div className='form-control'>
<label className='label'>
<span className='label-text font-bold'>Entity ID</span>
</label>
<input type='text' defaultValue={namespaceEntityId} className='input-bordered input' disabled />
<div>
<label className='block mb-1 text-sm font-medium text-gray-700'>Entity ID</label>
<input
type='text'
defaultValue={namespaceEntityId}
disabled
className='w-full rounded-md border border-gray-300 bg-gray-100 px-3 py-2 text-sm text-gray-700'
/>
</div>
<div className='form-control col-span-2 w-full'>
<label className='label'>
<span className='label-text font-bold'>Certificate</span>
</label>
<div className='md:col-span-2'>
<label className='block mb-1 text-sm font-medium text-gray-700'>Certificate</label>
<textarea
className='textarea-bordered textarea h-48'
defaultValue={certificate}
disabled></textarea>
disabled
className='h-48 w-full rounded-md border border-gray-300 bg-gray-100 px-3 py-2 text-sm font-mono text-gray-700'
/>
</div>
</div>
</div>
<div className='alert alert-error'>
<div>
<span className='text-white'>Caution: Not for production use.</span>
</div>
{/* Warning */}
<div className='rounded-md border border-red-200 bg-red-50 p-4'>
<p className='text-sm font-medium text-red-900'>Caution: Not for production use.</p>
</div>
</div>
</div>
@ -93,7 +110,7 @@ export const getServerSideProps: GetServerSideProps = async ({ params }) => {
return {
props: {
metadata,
params: params ? params : {},
params: params ?? {},
},
};
};

View File

@ -28,13 +28,9 @@ export default function Login() {
}
}, [acsUrl]);
const handleChange = (e: FormEvent<HTMLInputElement | HTMLSelectElement>): void => {
const handleChange = (e: FormEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value } = e.currentTarget;
setState({
...state,
[name]: value,
});
setState({ ...state, [name]: value });
};
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
@ -44,9 +40,7 @@ export default function Login() {
const response = await fetch(authUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: `${username}@${domain}`,
id,
@ -59,7 +53,6 @@ export default function Login() {
if (response.ok) {
const newDoc = document.open('text/html', 'replace');
newDoc.write(await response.text());
newDoc.close();
} else {
@ -72,112 +65,109 @@ export default function Login() {
<Head>
<title>Mock SAML Identity Provider - Login</title>
</Head>
<div className='flex min-h-full items-center justify-center'>
<div className='flex w-full max-w-xl flex-col px-3'>
<div className='space-y-2'>
<div className='border-2 p-4'>
<h2 className='mb-5 text-center text-2xl font-bold text-gray-900'>
{!acsUrl ? 'SAML IdP Login' : 'SAML SSO Login'}
</h2>
<form onSubmit={handleSubmit}>
<div className='grid grid-cols-2 gap-y-1 gap-x-5'>
{!acsUrl ? (
<div className='col-span-2'>
<div className='form-control'>
<label className='label'>
<span className='label-text font-bold'>ACS URL</span>
</label>
<input
type='text'
className='input input-bordered'
name='acsUrl'
id='acsUrl'
ref={acsUrlInp}
autoComplete='off'
placeholder='https://sso.eu.boxyhq.com/api/oauth/saml'
value={state.acsUrl}
onChange={handleChange}
/>
<label className='label'>
<span className='label-text-alt'>This is where we will post the SAML Response</span>
</label>
</div>
<div className='form-control col-span-2'>
<label className='label'>
<span className='label-text font-bold'>Audience</span>
</label>
<input
type='text'
className='input input-bordered'
name='audience'
id='audience'
autoComplete='off'
placeholder='https://saml.boxyhq.com'
value={state.audience}
onChange={handleChange}
/>
</div>
<div className='flex min-h-screen justify-center bg-white pt-12'>
<div className='w-full max-w-xl px-3 space-y-4'>
{/* Card */}
<div className='rounded-lg border border-gray-200 bg-white p-4 shadow-sm'>
<h2 className='mb-6 text-center text-2xl font-semibold text-gray-900'>
{!acsUrl ? 'SAML IdP Login' : 'SAML SSO Login'}
</h2>
<form onSubmit={handleSubmit}>
<div className='grid grid-cols-2 gap-x-5 gap-y-3'>
{!acsUrl && (
<div className='col-span-2 space-y-3'>
<div>
<label className='block mb-1 text-sm font-medium text-gray-700'>ACS URL</label>
<input
ref={acsUrlInp}
name='acsUrl'
id='acsUrl'
type='text'
autoComplete='off'
value={state.acsUrl}
onChange={handleChange}
placeholder='https://sso.eu.boxyhq.com/api/oauth/saml'
className='w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:ring-2 focus:ring-primary/30'
/>
<p className='mt-1 text-xs text-gray-500'>
This is where we will post the SAML Response
</p>
</div>
<div>
<label className='block mb-1 text-sm font-medium text-gray-700'>Audience</label>
<input
name='audience'
id='audience'
type='text'
autoComplete='off'
value={state.audience}
onChange={handleChange}
placeholder='https://saml.boxyhq.com'
className='w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:ring-2 focus:ring-primary/30'
/>
</div>
) : null}
<div className='form-control'>
<label className='label'>
<span className='label-text font-bold'>Email</span>
</label>
<input
name='username'
id='username'
ref={emailInp}
autoComplete='off'
type='text'
placeholder='jackson'
value={state.username}
onChange={handleChange}
className='input input-bordered'
title='Please provide a mock email address'
/>
</div>
<div className='form-control'>
<label className='label'>
<span className='label-text font-bold'>Domain</span>
</label>
<select
name='domain'
id='domain'
className='select select-bordered'
onChange={handleChange}
value={state.domain}>
<option value='example.com'>@example.com</option>
<option value='example.org'>@example.org</option>
</select>
</div>
<div className='form-control col-span-2'>
<label className='label'>
<span className='label-text font-bold'>Password</span>
</label>
<input
id='password'
autoComplete='off'
type='password'
defaultValue='samlstrongpassword'
className='input input-bordered'
/>
<label className='label'>
<span className='label-text-alt'>Any password works</span>
</label>
</div>
<button className='btn btn-primary col-span-2 block'>Sign In</button>
)}
<div>
<label className='block mb-1 text-sm font-medium text-gray-700'>Email</label>
<input
ref={emailInp}
name='username'
id='username'
type='text'
autoComplete='off'
value={state.username}
onChange={handleChange}
placeholder='jackson'
className='w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:ring-2 focus:ring-primary/30'
/>
</div>
</form>
</div>
<div className='alert alert-info'>
<div>
<span className='text-sm text-white'>
This is a simulated login screen, feel free to pick any username but you are restricted to
two domains example.com and example.org. But this should allow you to test all combinations
of your authentication and user modelling.
</span>
<div>
<label className='block mb-1 text-sm font-medium text-gray-700'>Domain</label>
<select
name='domain'
id='domain'
value={state.domain}
onChange={handleChange}
className='w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:ring-2 focus:ring-primary/30'>
<option value='example.com'>@example.com</option>
<option value='example.org'>@example.org</option>
</select>
</div>
<div className='col-span-2'>
<label className='block mb-1 text-sm font-medium text-gray-700'>Password</label>
<input
id='password'
type='password'
autoComplete='off'
defaultValue='samlstrongpassword'
className='w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:ring-2 focus:ring-primary/30'
/>
<p className='mt-1 text-xs text-gray-500'>Any password works</p>
</div>
<button
type='submit'
className='col-span-2 mt-2 rounded-md bg-primary px-4 py-2 text-sm font-semibold text-white hover:bg-primary-hover focus:outline-none focus:ring-2 focus:ring-primary/40'>
Sign In
</button>
</div>
</div>
</form>
</div>
{/* Info box */}
<div className='rounded-md border border-blue-200 bg-blue-50 p-4'>
<p className='text-sm text-blue-900'>
This is a simulated login screen. You may choose any username, but only the domains{' '}
<code className='font-mono'>example.com</code> and{' '}
<code className='font-mono'>example.org</code> are allowed.
</p>
</div>
</div>
</div>

View File

@ -1,6 +1,3 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
plugins: ['@tailwindcss/postcss'],
};

View File

@ -1,48 +1,39 @@
/* globals.css */
@import 'tailwindcss';
/* Tailwind base, components, utilities */
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
@theme {
/* Brand */
--color-primary: oklch(51.1% 0.262 276.966);
--color-primary-hover: oklch(47% 0.262 276.966);
/* Soft / UI surfaces */
--color-primary-soft: oklch(51.1% 0.262 276.966 / 0.1);
--color-primary-muted: oklch(51.1% 0.262 276.966 / 0.6);
/* Focus ring */
--color-primary-ring: oklch(51.1% 0.262 276.966 / 0.4);
}
/* Optional body defaults */
body {
padding: 0;
background-color: white;
font-family: ui-sans-serif, system-ui, sans-serif;
color: #1f2937; /* Tailwind gray-900 default */
line-height: 1.5;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell,
'Helvetica Neue', sans-serif;
height: 100%;
padding: 0;
}
#__next {
height: 100%;
/* Optional focus outline for accessibility */
:focus {
outline: 2px solid transparent;
outline-offset: 2px;
ring-offset-width: 2px;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
@layer components {
.alert {
@apply rounded;
}
.input {
@apply rounded;
}
.textarea {
@apply rounded;
}
.select {
@apply rounded;
}
.btn {
@apply rounded;
text-transform: none;
}
}
/* You can define custom colors directly in tailwind.config.js instead of here */

View File

@ -1,8 +1,5 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: 'class',
content: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
plugins: [require('daisyui')],
daisyui: {
themes: ['light'],
},
plugins: [],
};