Skip to content

Commit 98274d9

Browse files
authored
Merge pull request #69 from olillin/successful-startup
Improve DX by creating development environment with minimal and documented setup
2 parents 30145dc + 63ac4af commit 98274d9

File tree

18 files changed

+338
-79
lines changed

18 files changed

+338
-79
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ yarn-error.log*
3636
# typescript
3737
*.tsbuildinfo
3838

39-
/data
39+
/data
40+
/dev/data

CONTRIBUTING.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Contributing
2+
3+
Vad kul att du vill hjälpa till och utveckla nollk.it! Alla förbättringar leder
4+
till en bättre upplevelse för både Nollan och NollKIT, så tack!
5+
6+
## Information om projektet
7+
8+
Projektet använder teknologierna i listan nedanför. Du behöver inte vara bekant
9+
med alla för att göra ändringar men det gör det självklart lättare desto mer du
10+
kan! Det skadar aldrig att försöka och du kan alltid fråga om hjälp.
11+
12+
- [Next.js](https://nextjs.org/)
13+
- [TypeScript](https://www.typescriptlang.org/)
14+
- [Node.js](https://nodejs.org/)
15+
- [Tailwind](https://tailwindcss.com/)
16+
- [Prisma](https://www.prisma.io/)
17+
- [Docker](https://www.docker.com/) ([Docker Compose](https://docs.docker.com/compose/))
18+
- [MongoDB](https://www.mongodb.com/)
19+
20+
## För att utveckla
21+
22+
Installera [Docker Compose](https://docs.docker.com/compose/install) och
23+
[Node.js](https://nodejs.org/en/download) om du inte redan har det.
24+
25+
### Starta databasen
26+
27+
```console
28+
docker compose --file dev/docker-compose.yml up -d
29+
```
30+
31+
> [!WARNING]
32+
> Om du får ett felmeddelande, [kontrollera att Docker körs](https://docs.docker.com/engine/daemon/troubleshoot/).
33+
34+
### Förbered webbsidan
35+
36+
1. Skapa filen `.env` med följande innehåll:
37+
38+
```env
39+
DATABASE_URL=mongodb://localhost:27017/db
40+
PASSWORD=123
41+
```
42+
43+
2. Kör dessa kommandon:
44+
45+
```console
46+
npm install -D
47+
npx prisma generate
48+
node data.js
49+
```
50+
51+
> [!NOTE]
52+
> `npm install -D` installerar alla Node dependencies, inklusive dependencies för utveckling.
53+
> `npx prisma generate` genererar koden för Prisma klienten.
54+
> `node data.js` skapar startdata i databasen.
55+
56+
### Starta webbsidan
57+
58+
```console
59+
npm run dev
60+
```
61+
62+
> [!TIP]
63+
> Sidan uppdateras automatiskt när du ändrar källkoden.
64+
65+
### Stäng av databasen
66+
67+
```compose
68+
docker compose --file dev/docker-compose.yml down
69+
```

README.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,3 @@ Döp filerna till `år.pdf` eller `år.ttf`.
1212

1313
För att starta projektet bygg docker nånting nånting
1414
Prisma generate? Gör det lätt att förstå helt enkelt.
15-
16-
17-
18-
## För att utveckla
19-
Först kör npm i, sen npx prisma generate om prisma skulle gnälla.
20-
21-
Hur gör man med databasen?
22-
Docker?
23-

components/Countdown.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ export default function Countdown({ criticalDates }: { criticalDates: string[] }
1010
seconds: number,
1111
}
1212

13-
const [timeLeft, setTimeLeft] = useState<TimeLeft>({
13+
const defaultTimeLeft: TimeLeft = {
1414
days: 100,
1515
hours: 0,
1616
minutes: 0,
1717
seconds: 0,
18-
})
18+
}
19+
20+
const [timeLeft, setTimeLeft] = useState<TimeLeft>(defaultTimeLeft)
1921

2022
const ctx = useContext(YearContext)
2123

@@ -26,7 +28,10 @@ export default function Countdown({ criticalDates }: { criticalDates: string[] }
2628
function getTimeLeft(year: string): TimeLeft {
2729
const todaysDate = new Date()
2830

29-
const msDelta = new Date(findCriticalDate(criticalDates, year)).getTime() - todaysDate.getTime()
31+
const criticalDate = findCriticalDate(criticalDates, year)
32+
if (criticalDate === "")
33+
return defaultTimeLeft
34+
const msDelta = new Date(criticalDate).getTime() - todaysDate.getTime()
3035

3136
const daysLeft = Math.floor(msDelta / (86_400_000))
3237
const hoursLeft = Math.floor((msDelta % 86_400_000) / 3_600_000)

components/ImageWithFallback.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,16 @@ export default function ImageWithFallback(props: ImageWithFallbackProps) {
2121

2222
return useFallback ?
2323
// If fallbacksrc is not set, don't render anything
24-
props.fallbacksrc ? (
25-
<Image {...props} src={props.fallbacksrc} />
24+
(
25+
props.fallbacksrc &&
26+
typeof props.fallbacksrc === 'string' &&
27+
props.fallbacksrc.trim() !== ''
28+
) ? (
29+
<Image {...props} unoptimized src={props.fallbacksrc} />
2630
) : (
2731
null
2832
)
2933
: (
30-
<Image {...props} onError={() => setUseFallback(true)}/>
34+
<Image {...props} unoptimized onError={() => setUseFallback(true)}/>
3135
)
3236
}

components/ManagementDisplays/CommitteeManagementDisplay.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import TextInput from "../admin/TextInput"
66
import Accordion from "../admin/Accordion"
77

88
interface CommitteeManagementDisplayProps {
9-
committee: CommitteeWithMembers
9+
committee?: CommitteeWithMembers
1010
}
1111

1212
export default function CommitteeManagementDisplay({ committee }: CommitteeManagementDisplayProps) {
13+
if (!committee) {
14+
return <p>Skapa en kommittée för att komma igång.</p>
15+
}
1316

1417
const [members, setMembers] = useState(committee.members)
1518
let newMembers = [...members]
@@ -155,15 +158,21 @@ export default function CommitteeManagementDisplay({ committee }: CommitteeManag
155158
"Content-Type": "application/json",
156159
},
157160
body: JSON.stringify({ year: committee.year }),
158-
}).then(() => {
161+
}).then(() =>
159162
fetch("/api/admin/committee/add", {
160163
method: "POST",
161164
headers: {
162165
"Content-Type": "application/json",
163166
},
164167
body: JSON.stringify(committeeWithMembers),
165168
})
166-
}).then(() => alert("Sparat " + committee.year + " till databasen!"))
169+
).then(res => {
170+
if (res.status === 200) {
171+
alert("Sparat " + committee.year + " till databasen!")
172+
} else {
173+
alert("Något gick fel")
174+
}
175+
})
167176
}}>
168177
Spara till databasen
169178
</Button>

components/ManagementDisplays/TimelineManagementDisplay.tsx

Lines changed: 135 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@ interface TimelineManagementDisplayProps {
1111

1212
export default function TextManagementDisplay(props: TimelineManagementDisplayProps) {
1313

14-
const [selectedYear, setSelectedYear] = useState(props.committees.sort((a, b) => b.year - a.year)[0].year)
14+
const [selectedYear, setSelectedYear] = useState(props.committees.sort((a, b) => b.year - a.year)[0]?.year ?? new Date().getFullYear())
1515

1616
const [shownEvents, setShownEvents] = useState(props.timelineEvents.filter(event => event.year === selectedYear.toString()).sort((a, b) => a.date > b.date ? 1 : -1))
1717

18-
const getCategoryColor = (categoryId: string) => {
19-
return props.categories.find(category => category.title === categoryId)?.color ?? props.categories[0].color
18+
const [shownCategories, setShownCategories] = useState<Category[]>(props.categories)
19+
20+
const [newCategory, setNewCategory] = useState<Category>({ title: "", color: "", })
21+
22+
const getCategoryColor = (categoryId: string): string | undefined => {
23+
return shownCategories.find(category => category.title === categoryId)?.color ?? shownCategories[0]?.color
2024
}
2125

2226
return <>
@@ -32,7 +36,10 @@ export default function TextManagementDisplay(props: TimelineManagementDisplayPr
3236
}
3337
</select>
3438

35-
<div className="flex flex-col gap-8 pb-8">
39+
<section className="mb-8">
40+
<h2 className="text-2xl mb-4">Tidslinje</h2>
41+
42+
<div className="flex flex-col gap-8 pb-8">
3643
{
3744
shownEvents.map((event, index) => {
3845
return (
@@ -85,49 +92,140 @@ export default function TextManagementDisplay(props: TimelineManagementDisplayPr
8592
)
8693
})
8794
}
95+
</div>
8896

89-
<div className="flex gap-4">
90-
<select id="category-select" className="p-1 rounded-md" style={{ backgroundColor: props.categories[0].color }} onChange={
91-
e => {
92-
e.target.style.backgroundColor = getCategoryColor(e.target.value)
93-
}
94-
}>
95-
{
96-
props.categories.map(category => {
97-
return <option style={{ backgroundColor: category.color }} key={category.title} value={category.title}>
98-
{category.title}
99-
</option>
100-
})
101-
}
102-
</select>
103-
104-
<Button action={() => {
105-
setShownEvents([...shownEvents, {
106-
id: "tempid",
107-
categoryId: (document.getElementById("category-select") as HTMLSelectElement).value,
108-
date: "",
109-
text: "",
110-
link: "",
111-
year: selectedYear.toString()
112-
}])
97+
{
98+
shownCategories.length === 0
99+
? <p>Inga kategorier tillagda. Lägg till en kategori nedan.</p>
100+
: <>
101+
<div className="flex gap-4">
102+
<select id="category-select" className="p-1 rounded-md" style={{ backgroundColor: shownCategories[0]?.color ?? "" }} onChange={
103+
e => {
104+
e.target.style.backgroundColor = getCategoryColor(e.target.value) ?? ""
105+
}
106+
}>
107+
{
108+
shownCategories.map(category => {
109+
return <option style={{ backgroundColor: category.color }} key={category.title} value={category.title}>
110+
{category.title}
111+
</option>
112+
})
113+
}
114+
</select>
115+
116+
<Button action={() => {
117+
setShownEvents([...shownEvents, {
118+
id: "tempid",
119+
categoryId: (document.getElementById("category-select") as HTMLSelectElement).value,
120+
date: "",
121+
text: "",
122+
link: "",
123+
year: selectedYear.toString()
124+
}])
125+
}
126+
}>
127+
Lägg till
128+
</Button>
129+
</div>
130+
131+
<div className="mt-8">
132+
<Button color="bg-green-500" action={() => {
133+
fetch('/api/admin/timeline/update', {
134+
method: 'POST',
135+
headers: {
136+
'Content-Type': 'application/json'
137+
},
138+
body: JSON.stringify(
139+
{
140+
events: shownEvents,
141+
year: selectedYear
142+
}
143+
)
144+
}).then(res => {
145+
if (res.status === 200) {
146+
alert("Sparat!")
147+
} else {
148+
alert("Något gick fel")
149+
}
150+
})
151+
}}>
152+
Spara
153+
</Button>
154+
</div>
155+
</>
156+
}
157+
</section>
158+
159+
<section className="mb-8">
160+
<h2 className="text-2xl mb-6">Kategorier</h2>
161+
<div className="flex flex-col gap-4 pb-4">
162+
{
163+
shownCategories.map((category, index) => {
164+
return (
165+
<div key={category.title} className="bg-black/50 p-5">
166+
167+
<div className="flex items-start justify-between">
168+
<div className="p-1 px-2" style={{ backgroundColor: getCategoryColor(category.title) }}>{category.title}</div>
169+
<Button color="bg-red-500" action={() => {
170+
const usedEvent = props.timelineEvents.find(event => event.categoryId === category.title)
171+
?? shownEvents.find(event => event.categoryId === category.title)
172+
if (usedEvent) {
173+
alert(`Kan inte ta bort kategori, används år ${usedEvent.year} med text "${usedEvent.text}"`)
174+
return
175+
}
176+
177+
const newShownCategories = [...shownCategories]
178+
newShownCategories.splice(index, 1)
179+
setShownCategories(newShownCategories)
180+
}}>
181+
Ta bort
182+
</Button>
183+
</div>
184+
185+
<TextInput placeholder="Färg" setValue={
186+
inputValue => {
187+
const newShownCategories = [...shownCategories]
188+
newShownCategories[index].color = inputValue
189+
setShownCategories(newShownCategories)
190+
}
191+
}>
192+
{category.color}
193+
</TextInput>
194+
</div>
195+
)
196+
})
113197
}
114-
}>
115-
Lägg till
116-
</Button>
117198
</div>
118199

119-
<div>
120-
200+
<span className="flex flex-row items-center gap-4">
201+
<TextInput
202+
placeholder="Titel på kategori"
203+
setValue={inputValue => {
204+
const changedCategory = { ...newCategory, title: inputValue }
205+
setNewCategory(changedCategory)
206+
}}
207+
>
208+
{newCategory.title}
209+
</TextInput>
210+
211+
<Button action={async () => {
212+
setShownCategories([...shownCategories, newCategory])
213+
setNewCategory({...newCategory, title: ""})
214+
}}>
215+
Lägg till
216+
</Button>
217+
</span>
218+
219+
<div className="mt-8">
121220
<Button color="bg-green-500" action={() => {
122-
fetch('/api/admin/timeline/update', {
221+
fetch('/api/admin/category/update', {
123222
method: 'POST',
124223
headers: {
125224
'Content-Type': 'application/json'
126225
},
127226
body: JSON.stringify(
128227
{
129-
events: shownEvents,
130-
year: selectedYear
228+
categories: shownCategories
131229
}
132230
)
133231
}).then(res => {
@@ -140,10 +238,7 @@ export default function TextManagementDisplay(props: TimelineManagementDisplayPr
140238
}}>
141239
Spara
142240
</Button>
143-
144241
</div>
145-
146-
</div >
147-
242+
</section>
148243
</>
149244
}
File renamed without changes.

0 commit comments

Comments
 (0)