mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-12 06:40:57 +04:30
Improve portfolio title images, branding, and super-admin domains UI.
Add aspect-ratio crop presets for portfolio title images, fixed card image placeholders with contained centering, Meshkee favicon/logo fallbacks, login branding updates, category FAB alignment, and super-admin website/domain SSL tooling. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
f566387c61
commit
db52a83f98
@@ -55,6 +55,12 @@
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.previewImgNatural {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.removeBtn {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
@@ -85,9 +91,15 @@
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 160px;
|
||||
max-height: 420px;
|
||||
background: #1e293b;
|
||||
}
|
||||
|
||||
.cropAreaPortrait {
|
||||
max-height: none;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.cropControls {
|
||||
padding: 14px 16px;
|
||||
display: flex;
|
||||
@@ -141,6 +153,11 @@
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.applyBtn:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.changeBtn {
|
||||
display: inline-flex;
|
||||
align-self: flex-start;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import Cropper, { type Area } from 'react-easy-crop'
|
||||
import { ImagePlus, X } from 'lucide-react'
|
||||
import { getCroppedImage } from '../utils/cropImage'
|
||||
@@ -7,6 +7,7 @@ import styles from './ImageCropper.module.css'
|
||||
interface ImageCropperProps {
|
||||
value: string | null
|
||||
onChange: (value: string | null) => void
|
||||
/** Crop aspect ratio. Defaults to square (1). */
|
||||
aspect?: number
|
||||
outputFormat?: 'jpeg' | 'png'
|
||||
accept?: string
|
||||
@@ -30,6 +31,12 @@ export function ImageCropper({
|
||||
const [zoom, setZoom] = useState(1)
|
||||
const [croppedArea, setCroppedArea] = useState<Area | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
setCrop({ x: 0, y: 0 })
|
||||
setZoom(1)
|
||||
setCroppedArea(null)
|
||||
}, [aspect])
|
||||
|
||||
const onCropComplete = useCallback((_: Area, pixels: Area) => {
|
||||
setCroppedArea(pixels)
|
||||
}, [])
|
||||
@@ -38,7 +45,12 @@ export function ImageCropper({
|
||||
const file = e.target.files?.[0]
|
||||
if (!file) return
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => setImageSrc(reader.result as string)
|
||||
reader.onload = () => {
|
||||
setImageSrc(reader.result as string)
|
||||
setCrop({ x: 0, y: 0 })
|
||||
setZoom(1)
|
||||
setCroppedArea(null)
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
e.target.value = ''
|
||||
}
|
||||
@@ -62,13 +74,26 @@ export function ImageCropper({
|
||||
onChange(null)
|
||||
}
|
||||
|
||||
const frameStyle = { aspectRatio: `${aspect}` as const }
|
||||
const isPortrait = aspect < 1
|
||||
const frameStyle: React.CSSProperties = isPortrait
|
||||
? {
|
||||
aspectRatio: `${aspect}`,
|
||||
height: 'min(480px, 65vh)',
|
||||
width: 'auto',
|
||||
maxWidth: '100%',
|
||||
marginInline: 'auto',
|
||||
}
|
||||
: {
|
||||
aspectRatio: `${aspect}`,
|
||||
width: '100%',
|
||||
maxHeight: 'min(420px, 55vh)',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
{value && !imageSrc && (
|
||||
<div className={styles.preview} style={frameStyle}>
|
||||
<img src={value} alt="Thumbnail preview" className={styles.previewImg} />
|
||||
<div className={styles.preview}>
|
||||
<img src={value} alt="Thumbnail preview" className={styles.previewImgNatural} />
|
||||
<button type="button" className={styles.removeBtn} onClick={removeThumbnail} aria-label="Remove thumbnail">
|
||||
<X size={16} />
|
||||
</button>
|
||||
@@ -86,8 +111,9 @@ export function ImageCropper({
|
||||
|
||||
{imageSrc && (
|
||||
<div className={styles.cropPanel}>
|
||||
<div className={styles.cropArea} style={frameStyle}>
|
||||
<div className={`${styles.cropArea} ${isPortrait ? styles.cropAreaPortrait : ''}`} style={frameStyle}>
|
||||
<Cropper
|
||||
key={String(aspect)}
|
||||
image={imageSrc}
|
||||
crop={crop}
|
||||
zoom={zoom}
|
||||
@@ -113,7 +139,12 @@ export function ImageCropper({
|
||||
<button type="button" className={styles.cancelBtn} onClick={cancelCrop}>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" className={styles.applyBtn} onClick={applyCrop}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.applyBtn}
|
||||
onClick={() => void applyCrop()}
|
||||
disabled={!croppedArea}
|
||||
>
|
||||
Apply Crop
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -34,14 +34,17 @@
|
||||
width: 100%;
|
||||
aspect-ratio: 3 / 2;
|
||||
overflow: hidden;
|
||||
background: rgba(148, 163, 184, 0.08);
|
||||
background: transparent;
|
||||
border-bottom: 1px solid rgba(148, 163, 184, 0.12);
|
||||
}
|
||||
|
||||
.image {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.imagePlaceholder {
|
||||
|
||||
Reference in New Issue
Block a user