mirror of
https://github.com/samkaraca/lazuri-doviguram.git
synced 2026-04-29 17:59:51 +00:00
basic landing page created
This commit is contained in:
parent
4a24c455cb
commit
00ce3bd85f
157
package-lock.json
generated
157
package-lock.json
generated
@ -20,7 +20,8 @@
|
||||
"nanoid": "^3.3.6",
|
||||
"next": "13.2.4",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
"react-dom": "18.2.0",
|
||||
"slugify": "^1.6.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.15.3",
|
||||
@ -28,6 +29,7 @@
|
||||
"@types/react-dom": "18.0.11",
|
||||
"eslint": "8.36.0",
|
||||
"eslint-config-next": "13.2.4",
|
||||
"sass": "^1.62.0",
|
||||
"typescript": "4.9.5"
|
||||
}
|
||||
},
|
||||
@ -1708,6 +1710,19 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
@ -1916,6 +1931,15 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"devOptional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
|
||||
@ -1936,7 +1960,7 @@
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
},
|
||||
@ -2025,6 +2049,45 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
|
||||
"devOptional": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
"glob-parent": "~5.1.2",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.10.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar/node_modules/glob-parent": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
@ -3192,7 +3255,7 @@
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
@ -3277,6 +3340,20 @@
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
@ -3717,6 +3794,12 @@
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz",
|
||||
"integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
@ -3818,6 +3901,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-binary-path": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-boolean-object": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
|
||||
@ -3891,7 +3986,7 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -3909,7 +4004,7 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
@ -3942,7 +4037,7 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
@ -4744,6 +4839,15 @@
|
||||
"node-gyp-build-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"devOptional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@ -5024,7 +5128,7 @@
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
@ -5275,6 +5379,18 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/regenerator-runtime": {
|
||||
"version": "0.13.11",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||
@ -5442,6 +5558,23 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.62.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.62.0.tgz",
|
||||
"integrity": "sha512-Q4USplo4pLYgCi+XlipZCWUQz5pkg/ruSSgJ0WRDSb/+3z9tXUOkQ7QPYn4XrhZKYAK4HlpaQecRwKLJX6+DBg==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.23.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
|
||||
@ -5508,6 +5641,14 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/slugify": {
|
||||
"version": "1.6.6",
|
||||
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.6.tgz",
|
||||
"integrity": "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==",
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
|
||||
@ -5819,7 +5960,7 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
|
||||
@ -21,7 +21,8 @@
|
||||
"nanoid": "^3.3.6",
|
||||
"next": "13.2.4",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0"
|
||||
"react-dom": "18.2.0",
|
||||
"slugify": "^1.6.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.15.3",
|
||||
@ -29,6 +30,7 @@
|
||||
"@types/react-dom": "18.0.11",
|
||||
"eslint": "8.36.0",
|
||||
"eslint-config-next": "13.2.4",
|
||||
"sass": "^1.62.0",
|
||||
"typescript": "4.9.5"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
public/family.jpg
Normal file
BIN
public/family.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
BIN
public/reading-kid.png
Normal file
BIN
public/reading-kid.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
BIN
public/working-man.jpg
Normal file
BIN
public/working-man.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
11
src/admin_panel/index.tsx
Normal file
11
src/admin_panel/index.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { Theme } from "./model/theme";
|
||||
import { View } from "./view";
|
||||
import { ViewModel } from "./view_model";
|
||||
|
||||
export function AdminPanel({ data }: { data: Theme[] }) {
|
||||
return (
|
||||
<ViewModel data={data}>
|
||||
<View />
|
||||
</ViewModel>
|
||||
);
|
||||
}
|
||||
28
src/admin_panel/model/dialog.ts
Normal file
28
src/admin_panel/model/dialog.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { Theme, ThemeLevel } from "./theme";
|
||||
|
||||
export class IDialog implements Omit<Theme, "image" | "level" | "code"> {
|
||||
readonly isOpen: boolean;
|
||||
readonly title: string;
|
||||
readonly description: string;
|
||||
readonly image: File | null;
|
||||
readonly level: ThemeLevel | undefined;
|
||||
|
||||
constructor(dialog: Partial<IDialog>) {
|
||||
this.isOpen = dialog.isOpen ?? false;
|
||||
this.title = dialog.title ?? "";
|
||||
this.description = dialog.description ?? "";
|
||||
this.image = dialog.image ?? null;
|
||||
this.level = dialog.level ?? undefined;
|
||||
}
|
||||
|
||||
open = () => this.renewWith("isOpen", true);
|
||||
|
||||
close = () => this.renewWith("isOpen", false);
|
||||
|
||||
renewWith<T extends keyof IDialog>(
|
||||
property: T,
|
||||
newValue: IDialog[T]
|
||||
): IDialog {
|
||||
return new IDialog({ ...this, [property]: newValue });
|
||||
}
|
||||
}
|
||||
11
src/admin_panel/model/theme.ts
Normal file
11
src/admin_panel/model/theme.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export type ThemeLevel = "beginning" | "intermediate" | "advanced";
|
||||
|
||||
export interface Theme {
|
||||
id?: string;
|
||||
created_at?: string;
|
||||
title: string;
|
||||
code: string;
|
||||
image: string;
|
||||
level: ThemeLevel;
|
||||
lessons?: { title: string }[];
|
||||
}
|
||||
10
src/admin_panel/model/view_model.ts
Normal file
10
src/admin_panel/model/view_model.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { IDialog } from "./dialog";
|
||||
import { Theme } from "./theme";
|
||||
|
||||
export interface IViewModel {
|
||||
data: Theme[];
|
||||
dialog: IDialog;
|
||||
setDialog: Dispatch<SetStateAction<IViewModel["dialog"]>>;
|
||||
saveNewTheme: () => void;
|
||||
}
|
||||
187
src/admin_panel/view/index.tsx
Normal file
187
src/admin_panel/view/index.tsx
Normal file
@ -0,0 +1,187 @@
|
||||
import { Add, AddPhotoAlternate } from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardActionArea,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
TextField,
|
||||
ToggleButton,
|
||||
ToggleButtonGroup,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { useViewModelContext } from "../view_model";
|
||||
import { ThemeCard } from "@/core/components/theme_card";
|
||||
import Image from "next/image";
|
||||
|
||||
export function View() {
|
||||
const { data } = useViewModelContext()!;
|
||||
|
||||
return (
|
||||
<Box sx={{ overflow: "scroll", height: "100vh" }}>
|
||||
<Box
|
||||
sx={{
|
||||
height: "40vh",
|
||||
backgroundColor: "rebeccapurple",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
top: "-40vh",
|
||||
width: "80%",
|
||||
margin: "auto",
|
||||
maxWidth: "1536px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h3"
|
||||
color="white"
|
||||
padding="1.5rem"
|
||||
marginBottom={"15vh"}
|
||||
>
|
||||
Admin Paneli
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fill, minmax(14rem, 18rem))",
|
||||
gridAutoRows: "1fr",
|
||||
justifyContent: "center",
|
||||
gap: "1rem",
|
||||
}}
|
||||
>
|
||||
{data.map((theme) => {
|
||||
const { id, image, title } = theme;
|
||||
return (
|
||||
<ThemeCard
|
||||
key={id}
|
||||
photo={`${process.env.NEXT_PUBLIC_SUPABASE_STORAGE!}${image}`}
|
||||
title={title}
|
||||
lessons={theme.lessons!.map((lesson) => lesson.title)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
<CreateNewThemeCard />
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export function CreateNewThemeCard() {
|
||||
const { dialog, setDialog, saveNewTheme } = useViewModelContext()!;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card sx={{ borderRadius: "1rem" }}>
|
||||
<CardActionArea
|
||||
onClick={() => setDialog((prev) => prev.open())}
|
||||
sx={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
rowGap: "1rem",
|
||||
}}
|
||||
>
|
||||
<Add fontSize="large" />
|
||||
<Typography>Yeni tema ekle</Typography>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
<Dialog
|
||||
open={dialog.isOpen}
|
||||
onClose={() => setDialog((prev) => prev.close())}
|
||||
>
|
||||
<DialogTitle>Yeni tema oluştur</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ width: "100%", aspectRatio: "100/60" }}>
|
||||
{dialog.image ? (
|
||||
<Box sx={{ position: "relative", width: "100%", height: "100%" }}>
|
||||
<Image
|
||||
alt="image-to-upload"
|
||||
src={URL.createObjectURL(dialog.image)}
|
||||
style={{ objectFit: "cover" }}
|
||||
fill
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() =>
|
||||
document
|
||||
.querySelector<HTMLInputElement>("#upload-image-input")!
|
||||
.click()
|
||||
}
|
||||
variant="outlined"
|
||||
sx={{ width: "100%", height: "100%" }}
|
||||
>
|
||||
<AddPhotoAlternate fontSize="large" />
|
||||
<input
|
||||
onChange={(e) => {
|
||||
setDialog((prev) =>
|
||||
prev.renewWith("image", e.target.files!.item(0))
|
||||
);
|
||||
}}
|
||||
type="file"
|
||||
id="upload-image-input"
|
||||
accept="image/jpeg"
|
||||
hidden
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
<ToggleButtonGroup
|
||||
value={dialog.level}
|
||||
sx={{ marginTop: "1rem" }}
|
||||
exclusive
|
||||
onChange={(e, value) =>
|
||||
setDialog((prev) => prev.renewWith("level", value))
|
||||
}
|
||||
>
|
||||
<ToggleButton value="beginning" color="success">
|
||||
BAŞLANGIÇ
|
||||
</ToggleButton>
|
||||
<ToggleButton value="intermediate" color="warning">
|
||||
ORTA
|
||||
</ToggleButton>
|
||||
<ToggleButton value="advanced" color="error">
|
||||
İLERİ
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
<TextField
|
||||
inputProps={{ maxLength: 25 }}
|
||||
autoComplete="off"
|
||||
margin="normal"
|
||||
label="Başlık"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={dialog.title}
|
||||
onChange={(e) =>
|
||||
setDialog((prev) => prev.renewWith("title", e.target.value))
|
||||
}
|
||||
/>
|
||||
<TextField
|
||||
inputProps={{ maxLength: 50 }}
|
||||
autoComplete="off"
|
||||
margin="normal"
|
||||
label="Açıklama"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={dialog.description}
|
||||
onChange={(e) =>
|
||||
setDialog((prev) => prev.renewWith("description", e.target.value))
|
||||
}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={saveNewTheme}>Kaydet</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
24
src/admin_panel/view_model/index.tsx
Normal file
24
src/admin_panel/view_model/index.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { ReactNode, createContext, useContext } from "react";
|
||||
import { useViewModel } from "./use_view_model";
|
||||
import { IViewModel } from "../model/view_model";
|
||||
import { Theme } from "../model/theme";
|
||||
|
||||
const ViewModelContext = createContext<IViewModel | null>(null);
|
||||
|
||||
export function ViewModel({
|
||||
children,
|
||||
data,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
data: Theme[];
|
||||
}) {
|
||||
const viewModel = useViewModel(data);
|
||||
|
||||
return (
|
||||
<ViewModelContext.Provider value={viewModel}>
|
||||
{children}
|
||||
</ViewModelContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useViewModelContext = () => useContext(ViewModelContext);
|
||||
48
src/admin_panel/view_model/use_view_model.tsx
Normal file
48
src/admin_panel/view_model/use_view_model.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { useState } from "react";
|
||||
import { IViewModel } from "../model/view_model";
|
||||
import { IDialog } from "../model/dialog";
|
||||
import { supabase } from "../../../lib/supabaseClient";
|
||||
import slugify from "slugify";
|
||||
import { Theme } from "../model/theme";
|
||||
|
||||
export function useViewModel(data: Theme[]): IViewModel {
|
||||
const [dialog, setDialog] = useState<IDialog>(new IDialog({}));
|
||||
|
||||
function saveNewTheme() {
|
||||
const slug = slugify(dialog.title, { strict: true, lower: true });
|
||||
|
||||
supabase.storage
|
||||
.from("themes")
|
||||
.upload(`/${slug}/photo.jpg`, dialog.image!, {
|
||||
upsert: true,
|
||||
})
|
||||
.then((res) => {
|
||||
const { title, description, level } = dialog;
|
||||
const imagePath = res.data?.path;
|
||||
|
||||
if (title && description && level && imagePath && slug) {
|
||||
const theme = {
|
||||
title,
|
||||
description,
|
||||
level,
|
||||
code: slug,
|
||||
image: `/themes/${imagePath}`,
|
||||
} as Theme;
|
||||
|
||||
supabase
|
||||
.from("themes")
|
||||
.insert([theme])
|
||||
.then((a) => console.log(a));
|
||||
}
|
||||
|
||||
setDialog(new IDialog({}));
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
dialog,
|
||||
setDialog,
|
||||
saveNewTheme,
|
||||
};
|
||||
}
|
||||
64
src/core/components/theme_card.tsx
Normal file
64
src/core/components/theme_card.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import { ArrowForward, StarsRounded } from "@mui/icons-material";
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
CardActionArea,
|
||||
CardContent,
|
||||
CardMedia,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
|
||||
export function ThemeCard({
|
||||
photo,
|
||||
title,
|
||||
lessons,
|
||||
}: {
|
||||
photo: string;
|
||||
title: string;
|
||||
lessons: string[];
|
||||
}) {
|
||||
return (
|
||||
<Card sx={{ borderRadius: "1rem" }}>
|
||||
<CardActionArea
|
||||
sx={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "start",
|
||||
}}
|
||||
>
|
||||
<CardMedia
|
||||
component="img"
|
||||
sx={{ aspectRatio: "100/60", width: "100%" }}
|
||||
image={photo}
|
||||
/>
|
||||
<CardContent sx={{ aspectRatio: "100/40", width: "100%" }}>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Box sx={{ overflowWrap: "anywhere" }}>
|
||||
<Typography variant="h5">{title}</Typography>
|
||||
<List>
|
||||
{lessons.map((lesson, index) => {
|
||||
return (
|
||||
<ListItem disablePadding key={index}>
|
||||
<ListItemIcon sx={{ minWidth: "2rem" }}>
|
||||
<StarsRounded fontSize="small" color="warning" />
|
||||
</ListItemIcon>
|
||||
<ListItemText disableTypography>
|
||||
<Typography variant="subtitle2">{lesson}</Typography>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
</Box>
|
||||
<ArrowForward />
|
||||
</Box>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@ -1,22 +1,26 @@
|
||||
import "@/styles/globals.css";
|
||||
import "@/styles/globals.scss";
|
||||
import { CssBaseline, ThemeProvider, createTheme } from "@mui/material";
|
||||
import type { AppProps } from "next/app";
|
||||
import { Ubuntu } from "next/font/google";
|
||||
import { Poppins } from "next/font/google";
|
||||
|
||||
const inter = Ubuntu({ subsets: ["latin-ext"], weight: ["300", "400", "700"] });
|
||||
const poppins = Poppins({
|
||||
subsets: ["latin-ext"],
|
||||
weight: ["300", "400", "600", "700"],
|
||||
});
|
||||
|
||||
const theme = createTheme({
|
||||
typography: {
|
||||
fontFamily: "inherit",
|
||||
fontWeightLight: "300",
|
||||
fontWeightRegular: "400",
|
||||
fontWeightMedium: "600",
|
||||
fontWeightBold: "700",
|
||||
},
|
||||
});
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
<main className={inter.className}>
|
||||
<main className={poppins.className}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<Component {...pageProps} />
|
||||
|
||||
@ -1,150 +1,15 @@
|
||||
import {
|
||||
Add,
|
||||
AddPhotoAlternate,
|
||||
ArrowForward,
|
||||
Close,
|
||||
} from "@mui/icons-material";
|
||||
import { Theme } from "@/admin_panel/model/theme";
|
||||
import { supabase } from "../../../../lib/supabaseClient";
|
||||
import {
|
||||
AppBar,
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardActionArea,
|
||||
CardContent,
|
||||
CardMedia,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
IconButton,
|
||||
Paper,
|
||||
TextField,
|
||||
Toolbar,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
|
||||
interface Theme {
|
||||
id: string;
|
||||
date: string;
|
||||
title: string;
|
||||
code: string;
|
||||
photo: string;
|
||||
}
|
||||
import { AdminPanel } from "@/admin_panel";
|
||||
|
||||
export default function ActivityFinderPage({ data }: { data: Theme[] }) {
|
||||
console.log("data => ", data);
|
||||
return (
|
||||
<Box>
|
||||
<Box sx={{ height: "35vh", backgroundColor: "dodgerblue" }}></Box>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
right: 0,
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
width: "50%",
|
||||
margin: "7% auto",
|
||||
backgroundColor: "red",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fill, minmax(14rem, 18rem))",
|
||||
gridAutoRows: "max-content",
|
||||
justifyContent: "center",
|
||||
gap: "1rem",
|
||||
}}
|
||||
>
|
||||
{data.map((theme) => {
|
||||
const { id, photo, title } = theme;
|
||||
return (
|
||||
<Card key={id} sx={{ borderRadius: "1rem" }}>
|
||||
<CardActionArea>
|
||||
<CardMedia
|
||||
component="img"
|
||||
sx={{ aspectRatio: "100/60", width: "100%" }}
|
||||
image={photo}
|
||||
/>
|
||||
<CardContent sx={{ aspectRatio: "100/40" }}>
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Box>
|
||||
<Typography variant="h5">{title}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Lizards Hayvanlar alemi çok özeldir Lorem, ipsum dolor
|
||||
sit amet
|
||||
</Typography>
|
||||
</Box>
|
||||
<ArrowForward />
|
||||
</Box>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
<Card sx={{ borderRadius: "1rem" }}>
|
||||
<CardActionArea
|
||||
sx={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
rowGap: "1rem",
|
||||
}}
|
||||
>
|
||||
<Add fontSize="large" />
|
||||
<Typography>Yeni tema ekle</Typography>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</Box>
|
||||
<Dialog open={true}>
|
||||
<DialogTitle>Subscribe</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ width: "100%", aspectRatio: "100/60" }}>
|
||||
<Button
|
||||
onClick={() =>
|
||||
document
|
||||
.querySelector<HTMLInputElement>("#upload-image-input")!
|
||||
.click()
|
||||
}
|
||||
variant="outlined"
|
||||
sx={{ width: "100%", height: "100%" }}
|
||||
>
|
||||
<AddPhotoAlternate fontSize="large" />
|
||||
<input
|
||||
type="file"
|
||||
id="upload-image-input"
|
||||
accept="image/jpeg"
|
||||
hidden
|
||||
/>
|
||||
</Button>
|
||||
</Box>
|
||||
<TextField
|
||||
autoComplete="off"
|
||||
margin="normal"
|
||||
label="Başlık"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
/>
|
||||
<TextField
|
||||
autoComplete="off"
|
||||
margin="normal"
|
||||
label="Açıklama"
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => {}}>Kaydet</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
return <AdminPanel data={data} />;
|
||||
}
|
||||
|
||||
export async function getServerSideProps() {
|
||||
const { data } = await supabase.from("themes").select("*");
|
||||
const { data, error } = await supabase
|
||||
.from("themes")
|
||||
.select("*, lessons(title)");
|
||||
|
||||
return {
|
||||
props: {
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import Head from "next/head";
|
||||
import { Inter } from "next/font/google";
|
||||
import { Box, Container, Typography } from "@mui/material";
|
||||
import Image from "next/image";
|
||||
import { EmojiObjectsOutlined } from "@mui/icons-material";
|
||||
import Link from "next/link";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
import styles from "../styles/home.module.scss";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
@ -11,10 +12,100 @@ export default function Home() {
|
||||
<title>Lazuri Doviguram</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</Head>
|
||||
<div id="main">
|
||||
<h1>Ana sayfa</h1>
|
||||
<Link href="/admin">Admin Paneli</Link>
|
||||
<div id={styles.main}>
|
||||
<div className={`container`}>
|
||||
<div
|
||||
className={`${styles.banner} ${styles["section-vertical-padding"]}`}
|
||||
>
|
||||
<div>
|
||||
<Typography variant="h2" fontWeight="bold" marginBottom="1.5rem">
|
||||
Lazuri Doviguram!
|
||||
</Typography>
|
||||
<Typography variant="h5" fontWeight="light" maxWidth="31rem">
|
||||
Tarihin en eski dönemlerinden beri Kafkas insanının duygularına
|
||||
tercüman olmuş, yaşamlarına eşlik etmiş bir dili, daha yakından
|
||||
tanımak ister misin?
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={styles["image-container"]}>
|
||||
<Image
|
||||
alt="reading kid"
|
||||
src="/reading-kid.png"
|
||||
fill
|
||||
style={{ objectFit: "contain" }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`container ${styles["section-vertical-padding"]}`}>
|
||||
<Typography variant="h4" fontWeight="medium" marginBottom={"2.5rem"}>
|
||||
Temel Lazca
|
||||
</Typography>
|
||||
<div className={styles["theme-card-container"]}>
|
||||
<ThemeCard />
|
||||
<ThemeCard />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ThemeCard() {
|
||||
const data = {
|
||||
title: "Çkuni Ocaği",
|
||||
lessons: [
|
||||
"Çkuni ocağişi nca",
|
||||
"Arteşi papu",
|
||||
"Ma nandidi miyonun",
|
||||
"Çkimi, skani, muşi",
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<Link href="#" className={styles["theme-card"]}>
|
||||
<div className={styles.content}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
fontSize="1.7rem"
|
||||
fontWeight="medium"
|
||||
color="#444444"
|
||||
>
|
||||
{data.title}
|
||||
</Typography>
|
||||
<div className={styles.item}>
|
||||
{data.lessons.map((lesson) => {
|
||||
return (
|
||||
<Box key={lesson} sx={{ display: "flex" }}>
|
||||
<EmojiObjectsOutlined sx={{ color: "#005E98" }} />
|
||||
<Typography variant="body1">{lesson}</Typography>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<Typography
|
||||
variant="button"
|
||||
className={styles.button}
|
||||
sx={{
|
||||
color: "#073042",
|
||||
border: "0.15rem solid black",
|
||||
borderRadius: "2rem",
|
||||
padding: "0.5rem 2rem",
|
||||
width: "min-content",
|
||||
transition: "0.3s",
|
||||
}}
|
||||
>
|
||||
Başla
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={styles["image-container"]}>
|
||||
<Image
|
||||
alt="working man"
|
||||
src="/working-man.jpg"
|
||||
style={{ objectFit: "cover" }}
|
||||
fill
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
5
src/styles/global-variables.scss
Normal file
5
src/styles/global-variables.scss
Normal file
@ -0,0 +1,5 @@
|
||||
$brp-xs: 0px;
|
||||
$brp-sm: 600px;
|
||||
$brp-md: 900px;
|
||||
$brp-lg: 1200px;
|
||||
$brp-xl: 1536px;
|
||||
@ -1,6 +0,0 @@
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #eeeeee;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
14
src/styles/globals.scss
Normal file
14
src/styles/globals.scss
Normal file
@ -0,0 +1,14 @@
|
||||
@use "./global-variables.scss" as gv;
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
max-width: gv.$brp-lg;
|
||||
padding: 0 1rem 0 1rem;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #eeeeee;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
69
src/styles/home.module.scss
Normal file
69
src/styles/home.module.scss
Normal file
@ -0,0 +1,69 @@
|
||||
#main {
|
||||
height: 100vh;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.banner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 0.15rem solid #dddddd;
|
||||
.image-container {
|
||||
width: 19rem;
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.section-vertical-padding {
|
||||
padding: 4.5rem 0 4.5rem 0;
|
||||
}
|
||||
|
||||
.theme-card-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
|
||||
.theme-card {
|
||||
transition: 0.25s;
|
||||
background-color: #d7effe;
|
||||
width: 48.5%;
|
||||
aspect-ratio: 16/9;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
text-decoration: none;
|
||||
color: unset;
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
width: 42%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 4% 0 4% 6%;
|
||||
|
||||
.item {
|
||||
padding-left: 5%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 1rem;
|
||||
height: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 14px 25px 0 rgba(60, 64, 67, 0.08),
|
||||
0 4px 13px 0 rgba(60, 64, 67, 0.12);
|
||||
|
||||
.button {
|
||||
background-color: white;
|
||||
border-color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user