Skip to content

Commit 3d81eef

Browse files
website: Space pages
1 parent c00501b commit 3d81eef

15 files changed

Lines changed: 825 additions & 27 deletions

File tree

integrations/spaces-go-sdk/spaces/errors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import (
77
var (
88
ErrSlugTooLong = errors.New("slug exceeds maximum length of 20 characters")
99
ErrSlugTooShort = errors.New("slug must be at least 3 characters long")
10+
ErrSlugInvalidFormat = errors.New("slug must consist of lowercase letters, numbers, and hyphens, and cannot start or end with a hyphen")
1011
ErrTitleTooLong = errors.New("title exceeds maximum length of 100 characters")
1112
ErrTitleTooShort = errors.New("title must be at least 3 characters long")
13+
ErrSummaryTooLong = errors.New("summary exceeds maximum length of 300 characters")
1214
ErrDescriptionTooLong = errors.New("description exceeds maximum length of 10000 characters")
1315
ErrSpaceNotFound = errors.New("space not found")
1416
)

integrations/spaces-go-sdk/spaces/model.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package spaces
22

33
import (
4+
"regexp"
45
"time"
56

67
"github.com/fancyinnovations/fancyspaces/integrations/idp-go-sdk/idp"
@@ -181,13 +182,26 @@ func (s *Space) Validate() error {
181182
return ErrSlugTooLong
182183
}
183184

185+
slugPattern := `^[a-z0-9]+(?:-[a-z0-9]+)*$`
186+
matched, err := regexp.MatchString(slugPattern, s.Slug)
187+
if err != nil {
188+
return err
189+
}
190+
if !matched {
191+
return ErrSlugInvalidFormat
192+
}
193+
184194
if len(s.Title) > 100 {
185195
return ErrTitleTooLong
186196
}
187197
if len(s.Title) < 3 {
188198
return ErrTitleTooShort
189199
}
190200

201+
if len(s.Summary) > 300 {
202+
return ErrSummaryTooLong
203+
}
204+
191205
if len(s.Description) > 10_000 {
192206
return ErrDescriptionTooLong
193207
}

services/core/cmd/local/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ func main() {
9494
a := idp.NewService(idp.Configuration{
9595
Broker: b,
9696
PublicKey: pubKey,
97-
ExcludedRoutes: []string{"*"},
97+
ExcludedRoutes: []string{},
9898
})
9999

100100
chain := alice.New(

services/core/internal/app/seed.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ func seedMinecraftPlugins(store *spacesStore.Store) {
192192
Enabled: false,
193193
},
194194
AnalyticsSettings: spaces.AnalyticsSettings{
195-
Enabled: false,
195+
Enabled: true,
196196
RequireWriteKey: false,
197197
WriteKey: "",
198198
},
@@ -238,7 +238,7 @@ func seedMinecraftPlugins(store *spacesStore.Store) {
238238
Enabled: false,
239239
},
240240
AnalyticsSettings: spaces.AnalyticsSettings{
241-
Enabled: false,
241+
Enabled: true,
242242
RequireWriteKey: false,
243243
WriteKey: "",
244244
},
@@ -284,7 +284,7 @@ func seedMinecraftPlugins(store *spacesStore.Store) {
284284
Enabled: false,
285285
},
286286
AnalyticsSettings: spaces.AnalyticsSettings{
287-
Enabled: false,
287+
Enabled: true,
288288
RequireWriteKey: false,
289289
WriteKey: "",
290290
},
@@ -332,7 +332,7 @@ func seedHytalePlugins(store *spacesStore.Store) {
332332
Enabled: false,
333333
},
334334
AnalyticsSettings: spaces.AnalyticsSettings{
335-
Enabled: false,
335+
Enabled: true,
336336
RequireWriteKey: false,
337337
WriteKey: "",
338338
},

services/core/internal/spaces/spaces.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func (s *Store) Create(creator *idp.User, req *CreateOrUpdateSpaceReq) (*spaces.
8686
if err != nil {
8787
return nil, fmt.Errorf("failed to get spaces for creator: %w", err)
8888
}
89-
if len(userSpaces) >= 10 {
89+
if len(userSpaces) >= 100 {
9090
return nil, fmt.Errorf("user has reached the maximum number of spaces allowed")
9191
}
9292
}
@@ -171,6 +171,7 @@ func (s *Store) Update(id string, req *CreateOrUpdateSpaceReq) error {
171171

172172
space.Slug = req.Slug
173173
space.Title = req.Title
174+
space.Summary = req.Summary
174175
space.Description = req.Description
175176
space.Categories = req.Categories
176177
space.IconURL = req.IconURL

services/core/packages/webclient/src/api/spaces/spaces.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,93 @@ export async function getDownloadCountForSpacePerVersion(spaceId: string): Promi
117117
return (await response.json()).versions as Record<string, number>;
118118
}
119119

120+
export async function createSpace(slug: string, title: string, summary: string, description: string, categories: string[], iconURL: string): Promise<Space> {
121+
const userStore = useUserStore();
122+
if (!userStore.isAuthenticated) {
123+
throw new Error("User is not logged in");
124+
}
125+
126+
const response = await fetch(
127+
`/api/v1/spaces`,
128+
{
129+
method: "POST",
130+
headers: {
131+
"Content-Type": "application/json",
132+
"Accept": "application/json",
133+
"Authorization": `Bearer ${userStore.token}`,
134+
},
135+
body: JSON.stringify({
136+
slug: slug,
137+
title: title,
138+
summary: summary,
139+
description: description,
140+
categories: categories,
141+
icon_url: iconURL,
142+
}),
143+
},
144+
);
145+
146+
if (!response.ok) {
147+
throw new Error("Failed to create space: " + await response.text());
148+
}
149+
150+
const space = await response.json();
151+
space.created_at = new Date(space.created_at);
152+
153+
return space as Space;
154+
}
155+
156+
export async function updateSpace(spaceID: string, slug: string, title: string, summary: string, description: string, categories: string[], iconURL: string): Promise<void> {
157+
const userStore = useUserStore();
158+
if (!userStore.isAuthenticated) {
159+
throw new Error("User is not logged in");
160+
}
161+
162+
const response = await fetch(
163+
`/api/v1/spaces/${spaceID}`,
164+
{
165+
method: "PUT",
166+
headers: {
167+
"Content-Type": "application/json",
168+
"Authorization": `Bearer ${userStore.token}`,
169+
},
170+
body: JSON.stringify({
171+
slug: slug,
172+
title: title,
173+
summary: summary,
174+
description: description,
175+
categories: categories,
176+
icon_url: iconURL,
177+
}),
178+
},
179+
);
180+
181+
if (!response.ok) {
182+
throw new Error("Failed to update space: " + await response.text());
183+
}
184+
}
185+
186+
export async function changeSpaceStatus(spaceID: string, toStatus: string): Promise<void> {
187+
const userStore = useUserStore();
188+
if (!userStore.isAuthenticated) {
189+
throw new Error("User is not logged in");
190+
}
191+
192+
const response = await fetch(
193+
`/api/v1/spaces/${spaceID}/status`,
194+
{
195+
method: "POST",
196+
headers: {
197+
"Content-Type": "application/json",
198+
"Authorization": `Bearer ${userStore.token}`,
199+
},
200+
body: JSON.stringify({
201+
to: toStatus
202+
}),
203+
},
204+
);
205+
206+
if (!response.ok) {
207+
throw new Error("Failed to change space status: " + await response.text());
208+
}
209+
}

services/core/packages/webclient/src/components/AppFooter.vue

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
<v-row justify="space-evenly">
1313
<v-col class="mr-16" md="auto">
1414
<h1 class="text-h3 mb-2">FancySpaces</h1>
15-
<p class="mb-4">Download platform for products by FancyInnovations.</p>
15+
<p class="mb-4">A platform every project needs.<br>Tasks, releases, analytics and more.</p>
1616
<p>&copy; 2025-{{ (new Date()).getFullYear() }} <span class="d-none d-sm-inline-block">FancyInnovations</span>
1717
</p>
1818
</v-col>
@@ -32,8 +32,11 @@
3232
</v-col>
3333

3434
<v-col md="auto">
35-
<h3 class="mb-2">Other products</h3>
36-
<a class="hide-link" href="https://fancyanalytics.net" target="_blank"><p>FancyAnalytics</p></a>
35+
<h3 class="mb-2">Features</h3>
36+
<a class="hide-link" href="https://fancyspaces.net/explore" target="_blank"><p>Projects</p></a>
37+
<a class="hide-link" href="https://fancyspaces.net/analytics" target="_blank"><p>Analytics</p></a>
38+
<a class="hide-link" href="/coming-soon" target="_blank"><p>Storage (soon)</p></a>
39+
<a class="hide-link" href="/coming-soon" target="_blank"><p>Cloud (soon)</p></a>
3740
</v-col>
3841

3942
<v-col md="auto">

services/core/packages/webclient/src/components/SpaceCard.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ onMounted(async () => {
8383
<p v-if="creator" class="text-body-2 link--hover"><RouterLink :to="'/users/'+creator.name">By: {{ creator?.name }}</RouterLink></p>
8484
<p class="text-body-2">Created {{ space?.created_at.toLocaleDateString() }}</p>
8585
<p class="text-body-2">Updated {{ latestVersion?.published_at.toLocaleDateString() || space?.created_at.toLocaleDateString() }}</p>
86-
<p class="text-body-2">{{ downloadCount }} downloads</p>
86+
<p v-if="downloadCount > 0" class="text-body-2">{{ downloadCount }} downloads</p>
8787
</div>
8888
</div>
8989

services/core/packages/webclient/src/components/SpaceSidebar.vue

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,21 @@ const isMember = computed(() => {
101101
<!-- title="Roadmap"-->
102102
<!-- />-->
103103

104-
<!-- <v-list-item-->
105-
<!-- :to="`/spaces/${space?.slug}/analytics`"-->
106-
<!-- link-->
107-
<!-- prepend-icon="mdi-chart-box-outline"-->
108-
<!-- title="Analyitics"-->
109-
<!-- />-->
104+
<v-list-item
105+
v-if="space?.storage_settings.enabled && isMember"
106+
:to="`/spaces/${space?.slug}/storage`"
107+
link
108+
prepend-icon="mdi-library-shelves"
109+
title="Storage"
110+
/>
110111

111-
<v-list-item
112-
v-if="space?.storage_settings.enabled && isMember"
113-
:to="`/spaces/${space?.slug}/storage`"
114-
link
115-
prepend-icon="mdi-library-shelves"
116-
title="Storage"
117-
/>
112+
<v-list-item
113+
v-if="space?.analytics_settings.enabled"
114+
:to="`/analytics/${space?.slug}`"
115+
link
116+
prepend-icon="mdi-chart-box-outline"
117+
title="Analytics"
118+
/>
118119

119120
<v-list-item
120121
v-if="space?.secrets_settings.enabled && isMember"
@@ -124,8 +125,8 @@ const isMember = computed(() => {
124125
title="Secrets"
125126
/>
126127

127-
<v-divider />
128-
<v-list-subheader>External links</v-list-subheader>
128+
<v-divider v-if="space && space.links.length > 0"/>
129+
<v-list-subheader v-if="space && space.links.length > 0">External Links</v-list-subheader>
129130

130131
<v-list-item
131132
v-for="(link) in space?.links" :key="link.name"
@@ -136,6 +137,33 @@ const isMember = computed(() => {
136137
target="_blank"
137138
/>
138139

140+
<v-divider v-if="isMember" />
141+
<v-list-subheader v-if="isMember">Settings</v-list-subheader>
142+
143+
<v-list-item
144+
v-if="isMember"
145+
:to="`/spaces/${space?.slug}/settings`"
146+
link
147+
prepend-icon="mdi-cog-outline"
148+
title="Settings"
149+
/>
150+
151+
<v-list-item
152+
v-if="isMember"
153+
:to="`/spaces/${space?.slug}/features`"
154+
link
155+
prepend-icon="mdi-star-outline"
156+
title="Features"
157+
/>
158+
159+
<v-list-item
160+
v-if="isMember"
161+
:to="`/spaces/${space?.slug}/members`"
162+
link
163+
prepend-icon="mdi-account-multiple-outline"
164+
title="Members"
165+
/>
166+
139167
</v-list>
140168
</v-navigation-drawer>
141169
</template>

services/core/packages/webclient/src/pages/account-settings.vue

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const showEditPasswordDialog = ref(false);
3333
const editedPassword = ref('');
3434
const editedPasswordConfirm = ref('');
3535
36+
const showDeleteDialog = ref(false);
37+
3638
onMounted(() => {
3739
useHead({
3840
title: `FancySpaces - Account Settings`,
@@ -278,13 +280,37 @@ function deleteAccount() {
278280
class="ml-4"
279281
color="error"
280282
variant="outlined"
281-
@click="deleteAccount"
283+
@click="showDeleteDialog = true"
282284
>
283285
Delete Account
284286
</v-btn>
285287
</v-col>
286288
</v-row>
287289
</v-container>
290+
291+
292+
<Dialog :shown="showDeleteDialog">
293+
<v-card
294+
max-width="500"
295+
>
296+
<v-card-title class="text-h6">Delete account</v-card-title>
297+
298+
<v-card-text>
299+
To delete your account, please reach out to our support team (via E-Mail or our Discord server) and provide them with your user id and email. We will then verify your identity and proceed with the deletion.
300+
</v-card-text>
301+
302+
<v-card-actions>
303+
<v-spacer></v-spacer>
304+
305+
<v-btn
306+
text
307+
@click="showDeleteDialog = false"
308+
>
309+
Close
310+
</v-btn>
311+
</v-card-actions>
312+
</v-card>
313+
</Dialog>
288314
</template>
289315

290316
<style scoped>

0 commit comments

Comments
 (0)