Skip to content

Commit 779f22a

Browse files
committed
added support for links in listing text content
1 parent 4c92309 commit 779f22a

File tree

14 files changed

+761
-88
lines changed

14 files changed

+761
-88
lines changed

apps/internal-portal/frontend/src/pages/ListingTextContent/ListingTextContentForm.tsx

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,44 @@ const ListingTextContentForm = () => {
8585
return
8686
}
8787

88-
// Check for empty content
89-
const hasEmptyContent = blocks.some((block) => !block.content.trim())
90-
if (hasEmptyContent) {
91-
toast.error('Alla block måste ha innehåll')
88+
// Validate blocks
89+
const hasInvalidBlock = blocks.some((block) => {
90+
if (block.type === 'link') {
91+
// Link blocks need both name and valid URL
92+
if (!block.name?.trim() || !block.url?.trim()) return true
93+
try {
94+
new URL(block.url)
95+
return false
96+
} catch {
97+
return true
98+
}
99+
} else {
100+
// Text blocks need content
101+
return !block.content?.trim()
102+
}
103+
})
104+
105+
if (hasInvalidBlock) {
106+
toast.error('Kontrollera att alla block har giltigt innehåll')
92107
return
93108
}
94109

95110
try {
96-
const contentBlocks = blocks.map(({ type, content }) => ({
97-
type,
98-
content,
99-
}))
111+
// Format blocks for API - remove the id field and ensure correct structure
112+
const contentBlocks = blocks.map((block) => {
113+
if (block.type === 'link') {
114+
return {
115+
type: block.type,
116+
name: block.name || '',
117+
url: block.url || '',
118+
}
119+
} else {
120+
return {
121+
type: block.type,
122+
content: block.content || '',
123+
}
124+
}
125+
})
100126

101127
if (isEditMode && rentalObjectCode) {
102128
await updateMutation.mutateAsync({

apps/internal-portal/frontend/src/pages/ListingTextContent/components/ContentBlockEditor.tsx

Lines changed: 93 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,28 @@ import { z } from 'zod'
1515

1616
type ContentBlockType = z.infer<typeof leasing.v1.ContentBlockTypeSchema>
1717

18+
// ContentBlock can be either a text block or a link block
1819
export interface ContentBlock {
1920
id: string
2021
type: ContentBlockType
21-
content: string
22+
// Text block fields
23+
content?: string
24+
// Link block fields
25+
name?: string
26+
url?: string
2227
}
2328

2429
interface ContentBlockEditorProps {
2530
block: ContentBlock
2631
index: number
27-
onUpdate: (id: string, field: 'type' | 'content', value: string) => void
32+
onUpdate: (
33+
id: string,
34+
field: 'type' | 'content' | 'name' | 'url',
35+
value: string
36+
) => void
2837
onDelete: (id: string) => void
2938
isDragging?: boolean
30-
dragListeners?: Record<string, any>
39+
dragListeners?: Record<string, unknown>
3140
}
3241

3342
const blockTypeLabels: Record<ContentBlockType, string> = {
@@ -36,6 +45,17 @@ const blockTypeLabels: Record<ContentBlockType, string> = {
3645
subtitle: 'Underrubrik',
3746
text: 'Text',
3847
bullet_list: 'Punktlista',
48+
link: 'Länk',
49+
}
50+
51+
const isValidUrl = (url: string): boolean => {
52+
if (!url.trim()) return true
53+
try {
54+
new URL(url)
55+
return true
56+
} catch {
57+
return false
58+
}
3959
}
4060

4161
export const ContentBlockEditor = ({
@@ -46,6 +66,9 @@ export const ContentBlockEditor = ({
4666
isDragging = false,
4767
dragListeners,
4868
}: ContentBlockEditorProps) => {
69+
const isLinkBlock = block.type === 'link'
70+
const urlValid = isLinkBlock ? isValidUrl(block.url || '') : true
71+
4972
return (
5073
<Paper
5174
elevation={isDragging ? 8 : 2}
@@ -108,28 +131,76 @@ export const ContentBlockEditor = ({
108131
<MenuItem value="bullet_list">
109132
{blockTypeLabels.bullet_list}
110133
</MenuItem>
134+
<MenuItem value="link">{blockTypeLabels.link}</MenuItem>
111135
</Select>
112136
</FormControl>
113137

114-
<TextField
115-
fullWidth
116-
multiline
117-
rows={
118-
block.type === 'headline' || block.type === 'subtitle' ? 2 : 4
119-
}
120-
value={block.content}
121-
onChange={(e) => onUpdate(block.id, 'content', e.target.value)}
122-
placeholder={
123-
block.type === 'bullet_list'
124-
? 'Skriv en punkt per rad...'
125-
: 'Skriv innehåll...'
126-
}
127-
helperText={
128-
block.type === 'bullet_list'
129-
? 'Skriv varje punkt på en ny rad'
130-
: ''
131-
}
132-
/>
138+
{isLinkBlock ? (
139+
// Link block: show name and URL fields
140+
<Box display="flex" gap={2}>
141+
<Box flex={1}>
142+
<Typography
143+
variant="caption"
144+
color="text.secondary"
145+
gutterBottom
146+
>
147+
Namn
148+
</Typography>
149+
<TextField
150+
fullWidth
151+
size="small"
152+
value={block.name || ''}
153+
onChange={(e) => onUpdate(block.id, 'name', e.target.value)}
154+
placeholder="T.ex. Virtuell visning"
155+
error={!!block.url?.trim() && !block.name?.trim()}
156+
helperText={
157+
!!block.url?.trim() && !block.name?.trim()
158+
? 'Namn krävs'
159+
: ''
160+
}
161+
/>
162+
</Box>
163+
<Box flex={2}>
164+
<Typography
165+
variant="caption"
166+
color="text.secondary"
167+
gutterBottom
168+
>
169+
URL
170+
</Typography>
171+
<TextField
172+
fullWidth
173+
size="small"
174+
value={block.url || ''}
175+
onChange={(e) => onUpdate(block.id, 'url', e.target.value)}
176+
placeholder="https://example.com"
177+
error={!urlValid}
178+
helperText={!urlValid ? 'Ogiltig URL-format' : ''}
179+
/>
180+
</Box>
181+
</Box>
182+
) : (
183+
// Text block: show content textarea
184+
<TextField
185+
fullWidth
186+
multiline
187+
rows={
188+
block.type === 'headline' || block.type === 'subtitle' ? 2 : 4
189+
}
190+
value={block.content || ''}
191+
onChange={(e) => onUpdate(block.id, 'content', e.target.value)}
192+
placeholder={
193+
block.type === 'bullet_list'
194+
? 'Skriv en punkt per rad...'
195+
: 'Skriv innehåll...'
196+
}
197+
helperText={
198+
block.type === 'bullet_list'
199+
? 'Skriv varje punkt på en ny rad'
200+
: ''
201+
}
202+
/>
203+
)}
133204
</Box>
134205

135206
{/* Delete Button */}

apps/internal-portal/frontend/src/pages/ListingTextContent/components/ContentBlocksList.tsx

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,38 @@ export const ContentBlocksList = ({
6767

6868
const handleUpdateBlock = (
6969
id: string,
70-
field: 'type' | 'content',
70+
field: 'type' | 'content' | 'name' | 'url',
7171
value: string
7272
) => {
73-
const updatedBlocks = blocks.map((block) =>
74-
block.id === id ? { ...block, [field]: value } : block
75-
)
73+
const updatedBlocks = blocks.map((block) => {
74+
if (block.id !== id) return block
75+
76+
// When changing type, reset the appropriate fields
77+
if (field === 'type') {
78+
if (value === 'link') {
79+
// Switching to link type - clear content, set name/url
80+
return {
81+
...block,
82+
type: value as ContentBlock['type'],
83+
content: undefined,
84+
name: '',
85+
url: '',
86+
}
87+
} else {
88+
// Switching to text type - clear name/url, set content
89+
return {
90+
...block,
91+
type: value as ContentBlock['type'],
92+
content: '',
93+
name: undefined,
94+
url: undefined,
95+
}
96+
}
97+
}
98+
99+
// Regular field update
100+
return { ...block, [field]: value }
101+
})
76102
onBlocksChange(updatedBlocks)
77103
}
78104

@@ -91,7 +117,7 @@ export const ContentBlocksList = ({
91117
alignItems="center"
92118
marginBottom={2}
93119
>
94-
<Typography variant="h6">Textelement ({blocks.length})</Typography>
120+
<Typography variant="h6">Innehållsblock ({blocks.length})</Typography>
95121
<Button
96122
variant="contained"
97123
startIcon={<AddIcon />}
@@ -114,15 +140,15 @@ export const ContentBlocksList = ({
114140
}}
115141
>
116142
<Typography color="text.secondary" gutterBottom>
117-
Inga textelement ännu
143+
Inga innehållsblock ännu
118144
</Typography>
119145
<Button
120146
variant="outlined"
121147
startIcon={<AddIcon />}
122148
onClick={handleAddBlock}
123149
size="small"
124150
>
125-
Lägg till din första text
151+
Lägg till ditt första block
126152
</Button>
127153
</Box>
128154
) : (
@@ -164,8 +190,9 @@ export const ContentBlocksList = ({
164190

165191
<Box marginTop={2}>
166192
<Typography variant="caption" color="text.secondary">
167-
Tips: Dra i handtaget (⋮⋮) för att ändra ordning på texterna.
168-
Ordningen påverkar hur annonsen visas.
193+
Tips: Dra i handtaget (⋮⋮) för att ändra ordning på blocken. Ordningen
194+
påverkar hur annonsen visas. Använd blocktypen "Länk" för att lägga
195+
till klickbara länkar.
169196
</Typography>
170197
</Box>
171198
</Box>

apps/internal-portal/frontend/src/pages/ListingTextContent/components/ListingPreview.tsx

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
import { Box, Paper, Typography } from '@mui/material'
1+
import { Box, Paper, Typography, Link } from '@mui/material'
22
import { leasing } from '@onecore/types'
33
import { z } from 'zod'
44

55
type ContentBlockType = z.infer<typeof leasing.v1.ContentBlockTypeSchema>
66

77
interface ContentBlockBase {
88
type: ContentBlockType
9-
content: string
9+
// Text block fields
10+
content?: string
11+
// Link block fields
12+
name?: string
13+
url?: string
1014
}
1115

1216
interface ListingPreviewProps {
@@ -51,7 +55,7 @@ export const ListingPreview = ({
5155
return (
5256
<Box key={index} sx={{ marginBottom: 1 }}>
5357
{renderParagraphs(
54-
block.content,
58+
block.content || '',
5559
{
5660
width: '100%',
5761
fontSize: '1rem',
@@ -109,7 +113,7 @@ export const ListingPreview = ({
109113
return (
110114
<Box key={index} sx={{ marginBottom: 1 }}>
111115
{renderParagraphs(
112-
block.content,
116+
block.content || '',
113117
{
114118
width: '100%',
115119
fontSize: '1rem',
@@ -122,7 +126,7 @@ export const ListingPreview = ({
122126
)
123127

124128
case 'bullet_list':
125-
const items = block.content
129+
const items = (block.content || '')
126130
.split('\n')
127131
.filter((line: string) => line.trim() !== '')
128132

@@ -181,6 +185,42 @@ export const ListingPreview = ({
181185
</Box>
182186
)
183187

188+
case 'link':
189+
const hasValidLink = block.name?.trim() && block.url?.trim()
190+
return (
191+
<Box key={index} sx={{ marginBottom: 1 }}>
192+
{hasValidLink ? (
193+
<Link
194+
href={block.url}
195+
target="_blank"
196+
rel="noopener noreferrer"
197+
sx={{
198+
color: '#951b81',
199+
textDecoration: 'underline',
200+
fontSize: '1rem',
201+
fontFamily: 'graphikRegular',
202+
'&:hover': {
203+
color: '#7a1669',
204+
},
205+
}}
206+
>
207+
{block.name}
208+
</Link>
209+
) : (
210+
<Typography
211+
color="text.secondary"
212+
sx={{
213+
fontSize: '1rem',
214+
fontFamily: 'graphikRegular',
215+
textDecoration: 'underline',
216+
}}
217+
>
218+
Länk...
219+
</Typography>
220+
)}
221+
</Box>
222+
)
223+
184224
default:
185225
return null
186226
}

apps/internal-portal/frontend/src/pages/ListingTextContent/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ const ListingTextContent = () => {
121121

122122
<Box>
123123
<Typography variant="body2" color="text.secondary">
124-
Antal textelement:{' '}
124+
Antal innehållsblock:{' '}
125125
<strong>{data.contentBlocks.length}</strong>
126126
</Typography>
127127
</Box>

0 commit comments

Comments
 (0)