Skip to content

Commit 33187c4

Browse files
committed
Update landing skeleton, update buying success/cancel page, add install PWA button, add dashboard redirect and add note views count
1 parent a04f578 commit 33187c4

17 files changed

Lines changed: 280 additions & 53 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,5 @@ yarn-error.log*
3535
next-env.d.ts
3636

3737
public/*.js
38-
public/*.map
38+
public/*.map
39+
certificates

convex/document.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,23 @@ export const removeCoverImage = mutation({
475475
}
476476
})
477477

478+
export const incrementViews = mutation({
479+
args: {
480+
id: v.id("documents"),
481+
},
482+
handler: async (ctx, args) => {
483+
const document = await ctx.db.get(args.id)
484+
485+
if (!document || !document.isPublished || document.isAcrhived) {
486+
return
487+
}
488+
489+
await ctx.db.patch(args.id, {
490+
views: (document.views ?? 0) + 1,
491+
})
492+
}
493+
})
494+
478495
export const getTestPage = query({
479496
handler: async (ctx) => {
480497
const document = await ctx.db.query("documents")

convex/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default defineSchema({
1717
isPublished: v.boolean(),
1818
lastEditor: v.optional(v.string()),
1919
verifed: v.optional(v.boolean()),
20+
views: v.optional(v.number()),
2021
})
2122
.index("by_user", ["userId"])
2223
.index("by_user_parent", ["userId", "parentDocument"])

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"private": true,
55
"scripts": {
66
"dev": "next dev",
7+
"dev-https": "next dev --experimental-https",
78
"build": "next build",
89
"start": "next start",
910
"lint": "next lint"

src/app/(landing)/_components/heading.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import { useConvexAuth } from "convex/react"
66
import Link from "next/link"
77
import Image from "next/image"
88
import { images } from "@/config/routing/image.route"
9+
import InstallPWA from "./install"
910

1011
export function Heading() {
1112
const { isAuthenticated } = useConvexAuth()
1213

1314
return (
14-
<section className="grid md:grid-cols-2 gap-8 items-center py-12 px-4">
15+
<section className="grid md:grid-cols-2 gap-8 items-center py-1 px-4">
1516
<div className="space-y-6 text-left">
1617
<h1 className="text-4xl sm:text-5xl md:text-6xl font-extrabold leading-tight">
1718
Новый уровень построения задач. Встречайте <span className="text-logo-yellow">N</span>
@@ -21,9 +22,12 @@ export function Heading() {
2122
Планируйте, синхронизируйте и работайте в команде в современной, комфортной среде — быстро и приятно
2223
</p>
2324

25+
<span className="space-x-2">
2426
<Link href={isAuthenticated ? pages.DASHBOARD() : pages.AUTH}>
2527
<Button className="mt-2">Перейти в Notter</Button>
2628
</Link>
29+
<InstallPWA/>
30+
</span>
2731
</div>
2832

2933
<div className="flex justify-center">
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Button } from "@/components/ui/button";
2+
import { Download } from "lucide-react";
3+
import React, { useEffect, useState } from "react";
4+
5+
type BeforeInstallPromptEvent = Event & {
6+
prompt: () => Promise<void>;
7+
userChoice?: Promise<{ outcome: "accepted" | "dismissed"; platform?: string }>;
8+
};
9+
10+
const InstallPWA = () => {
11+
const [supportsPWA, setSupportsPWA] = useState(false);
12+
const [promptInstall, setPromptInstall] = useState<BeforeInstallPromptEvent | null>(null);
13+
14+
useEffect(() => {
15+
const handler = (e: any) => {
16+
e.preventDefault();
17+
setSupportsPWA(true);
18+
setPromptInstall(e);
19+
};
20+
window.addEventListener("beforeinstallprompt", handler);
21+
22+
return () => window.removeEventListener("beforeinstallprompt", handler);
23+
}, []);
24+
25+
const onClick = async (evt: React.MouseEvent) => {
26+
evt.preventDefault();
27+
if (!promptInstall) return;
28+
try {
29+
await promptInstall.prompt();
30+
await promptInstall.userChoice?.then(() => null).catch(() => null);
31+
} finally {
32+
setPromptInstall(null);
33+
setSupportsPWA(false);
34+
}
35+
};
36+
37+
// if (!supportsPWA) return null;
38+
39+
return (
40+
<Button
41+
className="link-button"
42+
id="setup_button"
43+
aria-label="Установить приложение"
44+
title="Установить приложение"
45+
onClick={onClick}
46+
variant={"outline"}
47+
>
48+
Установить <Download/>
49+
</Button>
50+
);
51+
};
52+
53+
export default InstallPWA;

src/app/(landing)/_components/premium-card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default function PremiumCard({ title, price, className, icon, features, b
1313
)}
1414
<div>
1515
<h3 className="text-2xl font-semibold">{title}</h3>
16-
<div className="text-sm text-muted-foreground">{price}₽ / мес</div>
16+
<div className="text-sm text-muted-foreground">{price}₽ / навсегда</div>
1717
</div>
1818
</div>
1919

src/app/(landing)/page.tsx

Lines changed: 88 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,114 @@ import { Premium } from "./_components/premium";
77
import { useConvexAuth } from "convex/react";
88
import { useEffect, useState } from "react";
99
import { Skeleton } from "@/components/ui/skeleton";
10+
import { useRouter } from "next/navigation";
1011

1112
export default function Landing() {
1213
const { isLoading } = useConvexAuth()
14+
const router = useRouter()
1315
const [isReady, setIsReady] = useState(false)
1416

1517
useEffect(() => {
18+
const redirectValue = localStorage.getItem("redirect")
19+
if (redirectValue === "true") {
20+
router.replace("/dashboard")
21+
}
22+
1623
if (!isLoading) {
1724
setIsReady(true);
1825
}
19-
}, [isLoading])
26+
}, [isLoading, router])
2027

2128
if (!isReady) {
2229
return (
2330
<div className="min-h-full flex flex-col">
24-
<div className="flex flex-col items-center justify-center md:justify-start text-center gap-y-8 flex-1 px-6 pb-10 m-0 p-0">
31+
<div className="flex flex-col items-center justify-center md:justify-start text-center gap-y-8 flex-1 pb-10 m-0 p-0 ">
2532

26-
<div className="flex flex-row gap-10">
27-
<div className="flex justify-start flex-col gap-2">
28-
<Skeleton className="h-40 sm:h-56 md:h-72 w-full max-w-[500px]" />
29-
<Skeleton className="h-6 sm:h-7 md:h-8 w-48 sm:w-80 md:w-[600px]" />
33+
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 flex flex-col lg:flex-row items-center lg:items-start gap-6 lg:gap-10">
34+
<div className="w-full max-w-2xl flex justify-start flex-col gap-2">
35+
<Skeleton className="h-40 sm:h-56 md:h-72 w-full" />
36+
<Skeleton className="h-6 sm:h-7 md:h-8 w-full max-w-[600px] mt-4" />
3037
<Skeleton className="h-8 sm:h-10 md:h-12 w-24 sm:w-28 md:w-36" />
3138
</div>
3239

33-
<Skeleton className="h-96 w-96" />
40+
<Skeleton className="h-64 w-full max-w-[340px] sm:h-80 sm:max-w-[420px] lg:h-96 lg:w-96" />
41+
</div>
42+
43+
<div className="w-full max-w-[1050px] mx-auto px-4 sm:px-6">
44+
<div className="space-y-3">
45+
{Array.from({ length: 4 }).map((_, i) => (
46+
<div key={i} className="rounded-2xl border border-border/50 p-5 sm:p-6 py-6">
47+
<div className="flex flex-col items-center text-center md:flex-row md:items-center md:text-left gap-4 sm:gap-5">
48+
<Skeleton className="h-20 w-20 rounded-xl" />
49+
<div className="space-y-2 w-full md:flex-1 flex flex-col items-center md:items-start text-center md:text-left">
50+
<Skeleton className="h-7 w-full max-w-56" />
51+
<Skeleton className="h-4 w-full max-w-[640px]" />
52+
</div>
53+
</div>
54+
</div>
55+
))}
56+
</div>
3457
</div>
58+
59+
<div className="w-full max-w-6xl mx-auto p-6 flex justify-center items-center flex-col">
60+
<div className="w-full">
61+
<div className="flex flex-col items-center text-center">
62+
<div className="flex items-end gap-2 sm:gap-3 mb-3">
63+
<Skeleton className="h-10 sm:h-12 w-36 sm:w-48" />
64+
<Skeleton className="h-10 sm:h-12 w-20 sm:w-28" />
65+
</div>
66+
<Skeleton className="h-4 w-full max-w-2xl mb-6" />
67+
68+
<div className="flex items-center gap-3 sm:gap-4 mb-8">
69+
<Skeleton className="h-9 w-24 sm:w-32 rounded-md" />
70+
<Skeleton className="h-6 w-4" />
71+
<Skeleton className="h-9 w-24 sm:w-32 rounded-md" />
72+
</div>
73+
</div>
3574

36-
<div className="w-full flex flex-col items-center justify-center gap-y-10">
37-
<Skeleton className="h-[400px] md:h-[250px] w-full max-w-[1000px]" />
38-
<Skeleton className="h-[400px] md:h-[250px] w-full max-w-[1000px]" />
39-
<Skeleton className="h-[400px] md:h-[250px] w-full max-w-[1000px]" />
40-
<Skeleton className="h-[400px] md:h-[250px] w-full max-w-[1000px]" />
75+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-5 sm:gap-6 text-left mb-8">
76+
{Array.from({ length: 3 }).map((_, i) => (
77+
<div key={i} className="rounded-2xl border border-border/50 p-5 sm:p-6 min-h-[330px] flex flex-col">
78+
<div className="flex items-center gap-3 mb-5">
79+
<Skeleton className="h-8 w-8 rounded-lg" />
80+
<div className="space-y-2 flex-1">
81+
<Skeleton className="h-7 w-28" />
82+
<Skeleton className="h-4 w-20" />
83+
</div>
84+
</div>
85+
86+
<div className="space-y-3 flex-1">
87+
<Skeleton className="h-3.5 w-full" />
88+
<Skeleton className="h-3.5 w-[92%]" />
89+
<Skeleton className="h-3.5 w-[84%]" />
90+
<Skeleton className="h-3.5 w-[74%]" />
91+
</div>
92+
93+
<Skeleton className="h-9 w-full rounded-md mt-6" />
94+
</div>
95+
))}
96+
</div>
97+
98+
<div className="rounded-xl border border-border/50 overflow-hidden">
99+
<div className="grid grid-cols-4 gap-3 sm:gap-4 p-4 sm:p-5 border-b border-border/50">
100+
<Skeleton className="h-5 w-full max-w-44" />
101+
<Skeleton className="h-5 w-full max-w-16" />
102+
<Skeleton className="h-5 w-full max-w-16" />
103+
<Skeleton className="h-5 w-full max-w-20" />
104+
</div>
105+
106+
{Array.from({ length: 6 }).map((_, r) => (
107+
<div key={r} className="grid grid-cols-4 gap-3 sm:gap-4 p-4 sm:p-5 border-b border-border/40 last:border-b-0">
108+
<Skeleton className="h-4 w-full max-w-52" />
109+
<Skeleton className="h-4 w-full max-w-12" />
110+
<Skeleton className="h-4 w-full max-w-16" />
111+
<Skeleton className="h-4 w-full max-w-16" />
112+
</div>
113+
))}
114+
</div>
115+
</div>
41116
</div>
117+
42118
</div>
43119
<Footer/>
44120
</div>

src/app/(main)/_components/document-list.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export function DocumentList({
3737
})
3838

3939
const onRedirect = (documentId: string) => {
40-
router.push(pages.DASHBOARD(documentId)) // redirect to docId
40+
router.push(pages.DASHBOARD(documentId))
4141
}
4242

4343
if (documents === undefined) {

src/app/(main)/_components/menu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export function Menu({ documentId }: MenuProps) {
3939
userId: orgId,
4040
});
4141

42-
const [openModal, setOpenModal] = useState(false); // Стейт для открытия модального окна
42+
const [openModal, setOpenModal] = useState(false);
4343
const [profile, setProfile] = useState<User | Org | null>(null)
4444

4545
useEffect(() => {

0 commit comments

Comments
 (0)