basic landing page created

This commit is contained in:
Hikmet 2023-04-21 19:18:27 +03:00
parent 4a24c455cb
commit 00ce3bd85f
20 changed files with 734 additions and 166 deletions

157
package-lock.json generated
View File

@ -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"
},

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
public/reading-kid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

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
View 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>
);
}

View 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 });
}
}

View 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 }[];
}

View 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;
}

View 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>
</>
);
}

View 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);

View 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,
};
}

View 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>
);
}

View File

@ -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} />

View File

@ -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: {

View File

@ -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>
);
}

View File

@ -0,0 +1,5 @@
$brp-xs: 0px;
$brp-sm: 600px;
$brp-md: 900px;
$brp-lg: 1200px;
$brp-xl: 1536px;

View File

@ -1,6 +0,0 @@
body {
margin: 0;
background-color: #eeeeee;
height: 100vh;
overflow: hidden;
}

14
src/styles/globals.scss Normal file
View 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;
}

View 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;
}
}
}
}