fix: remove duplicate page titles, add OG meta for review links
All checks were successful
Deploy / deploy (push) Successful in 11s
All checks were successful
Deploy / deploy (push) Successful in 11s
- Removed h1 titles from Settings and Brands (already in Header) - Server injects og:title, og:description, og:image meta tags for /review-post/:token URLs so WhatsApp/Slack show proper previews Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -129,13 +129,7 @@ export default function Brands() {
|
|||||||
<div className="space-y-6 animate-fade-in">
|
<div className="space-y-6 animate-fade-in">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<p className="text-sm text-text-tertiary">{t('brands.manageBrands')}</p>
|
||||||
<h1 className="text-2xl font-bold text-text-primary flex items-center gap-3">
|
|
||||||
<Tag className="w-7 h-7 text-brand-primary" />
|
|
||||||
{t('brands.title')}
|
|
||||||
</h1>
|
|
||||||
<p className="text-sm text-text-tertiary mt-1">{t('brands.manageBrands')}</p>
|
|
||||||
</div>
|
|
||||||
{isSuperadminOrManager && (
|
{isSuperadminOrManager && (
|
||||||
<button
|
<button
|
||||||
onClick={openNewBrand}
|
onClick={openNewBrand}
|
||||||
|
|||||||
@@ -62,14 +62,7 @@ export default function Settings() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 animate-fade-in max-w-3xl">
|
<div className="space-y-6 animate-fade-in max-w-3xl">
|
||||||
{/* Header */}
|
<p className="text-sm text-text-tertiary">{t('settings.preferences')}</p>
|
||||||
<div>
|
|
||||||
<h1 className="text-2xl font-bold text-text-primary flex items-center gap-3">
|
|
||||||
<SettingsIcon className="w-7 h-7 text-brand-primary" />
|
|
||||||
{t('settings.title')}
|
|
||||||
</h1>
|
|
||||||
<p className="text-sm text-text-tertiary mt-1">{t('settings.preferences')}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* General Settings */}
|
{/* General Settings */}
|
||||||
<div className="bg-white rounded-xl border border-border overflow-hidden">
|
<div className="bg-white rounded-xl border border-border overflow-hidden">
|
||||||
|
|||||||
@@ -4695,6 +4695,52 @@ async function startServer() {
|
|||||||
if (process.env.NODE_ENV === 'production') {
|
if (process.env.NODE_ENV === 'production') {
|
||||||
const clientDist = path.join(__dirname, '..', 'client', 'dist');
|
const clientDist = path.join(__dirname, '..', 'client', 'dist');
|
||||||
app.use(express.static(clientDist));
|
app.use(express.static(clientDist));
|
||||||
|
|
||||||
|
// OG meta tags for public review links (WhatsApp, Slack, etc.)
|
||||||
|
app.get('/review-post/:token', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const indexHtml = require('fs').readFileSync(path.join(clientDist, 'index.html'), 'utf-8');
|
||||||
|
const posts = await nocodb.list('Posts', {
|
||||||
|
where: `(approval_token,eq,${sanitizeWhereValue(req.params.token)})`,
|
||||||
|
limit: 1,
|
||||||
|
});
|
||||||
|
if (posts.length === 0) return res.send(indexHtml);
|
||||||
|
const post = posts[0];
|
||||||
|
|
||||||
|
// Find first image attachment for thumbnail
|
||||||
|
let ogImage = '';
|
||||||
|
try {
|
||||||
|
const attachments = await nocodb.list('PostAttachments', {
|
||||||
|
where: `(post_id,eq,${post.Id})`,
|
||||||
|
limit: 10,
|
||||||
|
});
|
||||||
|
const img = attachments.find(a => (a.mime_type || '').startsWith('image/'));
|
||||||
|
if (img) {
|
||||||
|
const baseUrl = `${req.protocol}://${req.get('host')}`;
|
||||||
|
ogImage = img.url?.startsWith('http') ? img.url : `${baseUrl}${img.url}`;
|
||||||
|
}
|
||||||
|
} catch (e) { /* no attachments */ }
|
||||||
|
|
||||||
|
const title = post.title || 'Post Review';
|
||||||
|
const desc = post.description ? post.description.slice(0, 200) : 'Review and approve this post';
|
||||||
|
const escape = s => s.replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<');
|
||||||
|
|
||||||
|
const ogTags = `
|
||||||
|
<meta property="og:title" content="${escape(title)}" />
|
||||||
|
<meta property="og:description" content="${escape(desc)}" />
|
||||||
|
<meta property="og:type" content="article" />
|
||||||
|
<meta property="og:url" content="${req.protocol}://${req.get('host')}${req.originalUrl}" />${ogImage ? `
|
||||||
|
<meta property="og:image" content="${escape(ogImage)}" />` : ''}
|
||||||
|
<meta name="twitter:card" content="${ogImage ? 'summary_large_image' : 'summary'}" />`;
|
||||||
|
|
||||||
|
const html = indexHtml.replace('</head>', ogTags + '\n </head>');
|
||||||
|
res.send(html);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('OG meta error:', err.message);
|
||||||
|
res.sendFile(path.join(clientDist, 'index.html'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.get('*', (req, res) => {
|
app.get('*', (req, res) => {
|
||||||
if (!req.path.startsWith('/api')) {
|
if (!req.path.startsWith('/api')) {
|
||||||
res.sendFile(path.join(clientDist, 'index.html'));
|
res.sendFile(path.join(clientDist, 'index.html'));
|
||||||
|
|||||||
Reference in New Issue
Block a user