chore: initial commit of sso-mock SSO login service
Next.js + Mantine mock SSO service providing account/password, phone verify-code, and Feishu QR login. Issues a one-time sso_code that the external app exchanges for user info. Includes: - Feishu callback fix: guard against duplicate/concurrent /api/login submissions of the same single-use authorization code (feishuLogin once-guard + memoized onLoginSuccess to stop effect re-fire). - ADAPTATION_GUIDE.md: how to switch from the in-memory code store to the real backend auth/index + inner_get_user_info endpoints. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
176
app/theme.ts
Normal file
176
app/theme.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
createTheme,
|
||||
Input,
|
||||
MantineColorsTuple,
|
||||
type MantineTheme,
|
||||
} from '@mantine/core'
|
||||
|
||||
export const logoBlue = '#0080FF'
|
||||
export const logoBlueHover = '#006EE6'
|
||||
|
||||
const brandColors: MantineColorsTuple = [
|
||||
'#EAF4FF',
|
||||
'#D9EBFF',
|
||||
'#B6D8FF',
|
||||
'#8EC2FF',
|
||||
'#63AAFF',
|
||||
'#3393FF',
|
||||
logoBlue,
|
||||
logoBlueHover,
|
||||
'#005FC6',
|
||||
'#004897',
|
||||
]
|
||||
|
||||
const greyColors: MantineColorsTuple = [
|
||||
'#FFFFFF',
|
||||
'#F7F8F9',
|
||||
'#F1F2F4',
|
||||
'#DCDFE4',
|
||||
'#B5BBC2',
|
||||
'#8F979F',
|
||||
'#5D6872',
|
||||
'#47535F',
|
||||
'#1B2A38',
|
||||
'#0F151B',
|
||||
]
|
||||
|
||||
const darkColors: MantineColorsTuple = [
|
||||
'#FFFFFF',
|
||||
'#EBEEF7',
|
||||
'#D3D4D8',
|
||||
'#7E858F',
|
||||
'#3C4859',
|
||||
'#2B3648',
|
||||
'#212A39',
|
||||
'#1A222D',
|
||||
'#151C24',
|
||||
'#0D0E12',
|
||||
]
|
||||
|
||||
const successColors: MantineColorsTuple = [
|
||||
'#E6FCF5',
|
||||
'#C3FAE8',
|
||||
'#96F2D7',
|
||||
'#5BE7B6',
|
||||
'#38D9A9',
|
||||
'#20C997',
|
||||
'#0EA879',
|
||||
'#07815B',
|
||||
'#076B3F',
|
||||
'#043E22',
|
||||
]
|
||||
|
||||
export const theme = createTheme({
|
||||
primaryColor: 'brand',
|
||||
cursorType: 'pointer',
|
||||
colors: {
|
||||
brand: brandColors,
|
||||
success: successColors,
|
||||
grey: greyColors,
|
||||
dark: darkColors,
|
||||
},
|
||||
defaultRadius: 'md',
|
||||
radius: {
|
||||
xs: '2px',
|
||||
sm: '4px',
|
||||
md: '8px',
|
||||
lg: '12px',
|
||||
xl: '16px',
|
||||
xxl: '24px',
|
||||
},
|
||||
fontFamily:
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
||||
fontSizes: {
|
||||
xs: '0.75rem',
|
||||
sm: '0.875rem',
|
||||
md: '1rem',
|
||||
lg: '1.125rem',
|
||||
xl: '1.25rem',
|
||||
xxl: '1.5rem',
|
||||
},
|
||||
headings: {
|
||||
fontFamily:
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
||||
fontWeight: '700',
|
||||
sizes: {
|
||||
h1: { fontSize: '2.25rem', lineHeight: '1.2' },
|
||||
h2: { fontSize: '1.875rem', lineHeight: '1.3' },
|
||||
h3: { fontSize: '1.5rem', lineHeight: '1.35' },
|
||||
h4: { fontSize: '1.25rem', lineHeight: '1.4' },
|
||||
h5: { fontSize: '1.125rem', lineHeight: '1.45' },
|
||||
h6: { fontSize: '1rem', lineHeight: '1.5' },
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
xs: '8px',
|
||||
sm: '12px',
|
||||
md: '16px',
|
||||
lg: '24px',
|
||||
xl: '32px',
|
||||
xxl: '48px',
|
||||
},
|
||||
shadows: {
|
||||
xs: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
||||
sm: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
|
||||
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
||||
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
|
||||
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
|
||||
},
|
||||
breakpoints: {
|
||||
xs: '36em',
|
||||
sm: '48em',
|
||||
md: '62em',
|
||||
lg: '75em',
|
||||
xl: '88em',
|
||||
},
|
||||
defaultGradient: {
|
||||
from: 'brand',
|
||||
to: 'success',
|
||||
deg: 45,
|
||||
},
|
||||
components: {
|
||||
Button: {
|
||||
styles: (_theme: MantineTheme, props: Record<string, unknown>) => {
|
||||
const variant = props.variant
|
||||
if (variant === 'page-ghost') {
|
||||
return {
|
||||
root: {
|
||||
outline: 'none',
|
||||
height: 30,
|
||||
borderRadius: 4,
|
||||
border: '1px solid rgba(71, 81, 105, 0.2)',
|
||||
backgroundColor: '#FFFFFF',
|
||||
color: '#1D2129',
|
||||
paddingInline: 16,
|
||||
'&:hover': { backgroundColor: '#F7F8FA' },
|
||||
},
|
||||
label: { fontSize: 12, lineHeight: '18px', fontWeight: 400 },
|
||||
}
|
||||
}
|
||||
if (variant === 'page-primary') {
|
||||
return {
|
||||
root: {
|
||||
outline: 'none',
|
||||
height: 30,
|
||||
borderRadius: 4,
|
||||
border: 'none',
|
||||
backgroundColor: logoBlue,
|
||||
color: '#FFFFFF',
|
||||
paddingInline: 16,
|
||||
'&:hover': { backgroundColor: logoBlueHover },
|
||||
},
|
||||
label: { fontSize: 12, lineHeight: '18px', fontWeight: 500 },
|
||||
}
|
||||
}
|
||||
return { root: { outline: 'none' } }
|
||||
},
|
||||
},
|
||||
InputWrapper: Input.Wrapper.extend({
|
||||
defaultProps: {
|
||||
inputWrapperOrder: ['label', 'input', 'description', 'error'],
|
||||
},
|
||||
}),
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user